在模板调用中隐式地将 wrapper class 转换为 superclass
Implicitly cast wrapper class to supperclass in templated call
在设计 DSL(编译成 C++)时,我发现定义一个包装器 class 很方便,一旦销毁,将在包含的 [=56= 上调用 .free()
方法]:
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
包装器设计为完全透明:所有方法、重载和构造函数都继承自 T
(至少据我所知),但是当包含在包装器中时,将调用 free() 方法毁灭后。请注意,我明确避免为此使用 T
的析构函数,因为 T::free()
和 ~T()
可能具有不同的语义!
所有这些工作正常,直到包装的 class 被用作非引用模板化调用的成员,此时 freeOnDestroy
被实例化,在包装的对象上免费调用。我希望 发生的是让模板方法使用 T
而不是 freeOnDestroy<T>
,并将参数隐式转换为 supperclass .下面的代码示例说明了这个问题:
// First class that has a free (and will be used in foo)
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ cout << "free called!\n"; delete []arr; }
};
// Second class that has a free (and is also used in foo)
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
// Foo: in this case, accepts anything with operator[int]
// Foo cannot be assumed to be written as T &in!
// Foo in actuality may have many differently-templated parameters, not just one
template<typename T>
void foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c); // OK!
foo(v); // OK!
foo<C>(f_c); // OK, but the base (C) of f_c may not be explicitly known at the call site, for example, if f_c is itself received as a template
foo(f_c); // BAD: Creates a new freeOnDestroy<C> by implicit copy constructor, and uppon completion calls C::free, deleting arr! Would prefer it call foo<C>
foo(f_c); // OH NO! Tries to print arr, but it has been deleted by previous call! Segmentation fault :(
return 0;
}
我应该提到的一些非解决方案是:
- 使
freeOnDestroy::freeOnDestroy(const freeOnDestroy &src)
显式和私有,但这似乎覆盖了 T
的构造函数。我希望它会尝试将其隐式转换为 T
并将其用作模板参数。
- 假设
foo
收到其模板化参数的引用(如 void foo(T &in)
中所示:这既不是这种情况,在某些情况下也不理想
- 始终显式模板化对
foo
的调用,如 foo<C>(f_c)
:f_c
本身可能被模板化,因此很难知道用 [= 实例化 foo
30=](是的,这可以通过创建 foo
的多个版本来一个一个地删除包装器来完成,但是如果不为每个模板化参数创建不同的重载,我找不到这样做的方法foo
)。
总而言之,我的问题是:是否有一种干净的方法来确保在解析模板时将基 class 转换为其超 class?或者,如果没有,是否有某种使用 SFINAE 的方法,当模板参数是包装器 class 的实例时导致替换失败,从而强制它使用隐式转换到包装器 class(无需重复每个 foo
-like 方法签名可能数十次)?
我目前有一个涉及 DSL 更改的工作区,但我对此并不完全满意,并且很好奇是否有可能设计一个包装器 class 作为描述。
这里的问题不是 "wrapped class gets used as a member to a non-reference templated call"。
这里的问题是模板包装器——可能还有它的超级class——有violated the Rule Of Three.
将 class 的实例作为非引用参数传递只是 "passing by value" 的另一种说法。按值传递会复制 class 的实例。您的模板 class 及其包装的 class 很可能都没有显式复制构造函数;因此,class 的复制实例不知道它是一个副本,因此析构函数执行它认为应该执行的操作。
这里正确的解决方案不是破解某些东西,使按值传递 freeOnDestroy<T>
的实例最终复制 T
,而不是 freeOnDestroy<T>
。正确的解决方案是向 freeOnDestroy
模板以及可能使用它的任何 superclass 添加适当的复制构造函数和赋值运算符,以便一切都符合三规则。
您可以使用正确定义的检测器和 sfinaed 函数,如下所示:
#include<iostream>
#include<type_traits>
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
template<typename T>
struct FreeOnDestroyDetector: std::false_type { };
template<typename T>
struct FreeOnDestroyDetector<freeOnDestroy<T>>: std::true_type { };
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ std::cout << "free called!\n"; delete []arr; }
};
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
template<typename..., typename T>
std::enable_if_t<not FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T in) {
std::cout << "here you have not a freeOnDestroy based class" << std::endl;
}
template<typename..., typename T>
std::enable_if_t<FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T &in) {
std::cout << "here you have a freeOnDestroy based class" << std::endl;
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c);
foo(v);
foo<C>(f_c);
foo(f_c);
foo(f_c);
return 0;
}
正如您在 运行 示例中看到的那样,free
仅被调用一次,即针对在 main
函数中创建的 freeOnDestroy
。
如果你想明确禁止freeOnDestroy
作为参数,你可以使用单个函数作为以下函数:
template<typename..., typename T>
void foo(T &in) {
static_assert(not FreeOnDestroyDetector<std::decay_t<T>>::value, "!");
std::cout << "here you have a freeOnDestroy based class" << std::endl;
}
请注意,我添加了一个可变参数作为保护,这样就不能再使用 foo<C>(f_c);
强制使用类型。
如果您想允许这样的表达式,请将其删除。从问题上看不清楚。
一个解决方案,虽然有点难看,但似乎有效,是使用重载展开方法,例如:
template<typename T> T freeOnDestroyUnwrapper(const T &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<T> &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(T &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<T> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
然后,可以使用解包器进行调用:
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(freeOnDestroyUnwrapper(c));
foo(freeOnDestroyUnwrapper(v));
foo<C>(freeOnDestroyUnwrapper(f_c));
foo(freeOnDestroyUnwrapper(f_c));
foo(freeOnDestroyUnwrapper(f_c));
return 0;
}
或者,为了不那么冗长,我们可以更改 foo
以便它为我们执行此操作:
template<typename T>
void _foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
template<typename... Ts>
void foo(Ts&&... args){
_foo(freeOnDestroyUnwrapper(args)...);
}
然后正常调用:
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c);
foo(v);
//foo<C>(f_c); // This now doesn't work!
foo(f_c);
foo(f_c);
return 0;
}
这似乎适用于 foo 可能具有的任意数量的参数(不同模板,如果需要的话),并且当 foo
s 输入是一个引用时(这在我的上下文中不会发生)似乎表现得很好,但为了使此解决方案通用化会更好)。
我不相信这是最好的解决方案,或者它可以推广到所有情况,另外,必须将所有声明加倍有点麻烦,而且对大多数 IDE 的自动完成功能来说是不透明的。欢迎更好的解决方案和改进!
在设计 DSL(编译成 C++)时,我发现定义一个包装器 class 很方便,一旦销毁,将在包含的 [=56= 上调用 .free()
方法]:
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
包装器设计为完全透明:所有方法、重载和构造函数都继承自 T
(至少据我所知),但是当包含在包装器中时,将调用 free() 方法毁灭后。请注意,我明确避免为此使用 T
的析构函数,因为 T::free()
和 ~T()
可能具有不同的语义!
所有这些工作正常,直到包装的 class 被用作非引用模板化调用的成员,此时 freeOnDestroy
被实例化,在包装的对象上免费调用。我希望 发生的是让模板方法使用 T
而不是 freeOnDestroy<T>
,并将参数隐式转换为 supperclass .下面的代码示例说明了这个问题:
// First class that has a free (and will be used in foo)
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ cout << "free called!\n"; delete []arr; }
};
// Second class that has a free (and is also used in foo)
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
// Foo: in this case, accepts anything with operator[int]
// Foo cannot be assumed to be written as T &in!
// Foo in actuality may have many differently-templated parameters, not just one
template<typename T>
void foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c); // OK!
foo(v); // OK!
foo<C>(f_c); // OK, but the base (C) of f_c may not be explicitly known at the call site, for example, if f_c is itself received as a template
foo(f_c); // BAD: Creates a new freeOnDestroy<C> by implicit copy constructor, and uppon completion calls C::free, deleting arr! Would prefer it call foo<C>
foo(f_c); // OH NO! Tries to print arr, but it has been deleted by previous call! Segmentation fault :(
return 0;
}
我应该提到的一些非解决方案是:
- 使
freeOnDestroy::freeOnDestroy(const freeOnDestroy &src)
显式和私有,但这似乎覆盖了T
的构造函数。我希望它会尝试将其隐式转换为T
并将其用作模板参数。 - 假设
foo
收到其模板化参数的引用(如void foo(T &in)
中所示:这既不是这种情况,在某些情况下也不理想 - 始终显式模板化对
foo
的调用,如foo<C>(f_c)
:f_c
本身可能被模板化,因此很难知道用 [= 实例化foo
30=](是的,这可以通过创建foo
的多个版本来一个一个地删除包装器来完成,但是如果不为每个模板化参数创建不同的重载,我找不到这样做的方法foo
)。
总而言之,我的问题是:是否有一种干净的方法来确保在解析模板时将基 class 转换为其超 class?或者,如果没有,是否有某种使用 SFINAE 的方法,当模板参数是包装器 class 的实例时导致替换失败,从而强制它使用隐式转换到包装器 class(无需重复每个 foo
-like 方法签名可能数十次)?
我目前有一个涉及 DSL 更改的工作区,但我对此并不完全满意,并且很好奇是否有可能设计一个包装器 class 作为描述。
这里的问题不是 "wrapped class gets used as a member to a non-reference templated call"。
这里的问题是模板包装器——可能还有它的超级class——有violated the Rule Of Three.
将 class 的实例作为非引用参数传递只是 "passing by value" 的另一种说法。按值传递会复制 class 的实例。您的模板 class 及其包装的 class 很可能都没有显式复制构造函数;因此,class 的复制实例不知道它是一个副本,因此析构函数执行它认为应该执行的操作。
这里正确的解决方案不是破解某些东西,使按值传递 freeOnDestroy<T>
的实例最终复制 T
,而不是 freeOnDestroy<T>
。正确的解决方案是向 freeOnDestroy
模板以及可能使用它的任何 superclass 添加适当的复制构造函数和赋值运算符,以便一切都符合三规则。
您可以使用正确定义的检测器和 sfinaed 函数,如下所示:
#include<iostream>
#include<type_traits>
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
template<typename T>
struct FreeOnDestroyDetector: std::false_type { };
template<typename T>
struct FreeOnDestroyDetector<freeOnDestroy<T>>: std::true_type { };
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ std::cout << "free called!\n"; delete []arr; }
};
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
template<typename..., typename T>
std::enable_if_t<not FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T in) {
std::cout << "here you have not a freeOnDestroy based class" << std::endl;
}
template<typename..., typename T>
std::enable_if_t<FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T &in) {
std::cout << "here you have a freeOnDestroy based class" << std::endl;
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c);
foo(v);
foo<C>(f_c);
foo(f_c);
foo(f_c);
return 0;
}
正如您在 运行 示例中看到的那样,free
仅被调用一次,即针对在 main
函数中创建的 freeOnDestroy
。
如果你想明确禁止freeOnDestroy
作为参数,你可以使用单个函数作为以下函数:
template<typename..., typename T>
void foo(T &in) {
static_assert(not FreeOnDestroyDetector<std::decay_t<T>>::value, "!");
std::cout << "here you have a freeOnDestroy based class" << std::endl;
}
请注意,我添加了一个可变参数作为保护,这样就不能再使用 foo<C>(f_c);
强制使用类型。
如果您想允许这样的表达式,请将其删除。从问题上看不清楚。
一个解决方案,虽然有点难看,但似乎有效,是使用重载展开方法,例如:
template<typename T> T freeOnDestroyUnwrapper(const T &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<T> &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(T &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<T> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
然后,可以使用解包器进行调用:
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(freeOnDestroyUnwrapper(c));
foo(freeOnDestroyUnwrapper(v));
foo<C>(freeOnDestroyUnwrapper(f_c));
foo(freeOnDestroyUnwrapper(f_c));
foo(freeOnDestroyUnwrapper(f_c));
return 0;
}
或者,为了不那么冗长,我们可以更改 foo
以便它为我们执行此操作:
template<typename T>
void _foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
template<typename... Ts>
void foo(Ts&&... args){
_foo(freeOnDestroyUnwrapper(args)...);
}
然后正常调用:
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c);
foo(v);
//foo<C>(f_c); // This now doesn't work!
foo(f_c);
foo(f_c);
return 0;
}
这似乎适用于 foo 可能具有的任意数量的参数(不同模板,如果需要的话),并且当 foo
s 输入是一个引用时(这在我的上下文中不会发生)似乎表现得很好,但为了使此解决方案通用化会更好)。
我不相信这是最好的解决方案,或者它可以推广到所有情况,另外,必须将所有声明加倍有点麻烦,而且对大多数 IDE 的自动完成功能来说是不透明的。欢迎更好的解决方案和改进!