【发布时间】:2022-01-12 23:20:01
【问题描述】:
我设计了一个更简单的包装类,它为对象添加标签,目的是隐式转换/能够替换被包装的对象。
#include <string>
#include <type_traits>
#include <utility>
template < typename T, typename Key = std::string >
class myTag{
T val;
public:
Key key;
template < typename... Args,
typename = typename std::enable_if< std::is_constructible< T, Args... >::value >::type >
myTag(Args&&... args) :
val(std::forward< Args >(args)...) {
std::cout << "forward ctor" << std::endl;
}
myTag(const myTag& other) :
key(other.key), val(other.val) {
std::cout << "copy ctor" << std::endl;
}
myTag(myTag&& other):
key(other.key), val(other.val) {
std::cout << "move ctor" << std::endl;
}
operator T&() { return val; }
operator const T&() const { return val; }
};
int main(int argc, char const *argv[]) {
myTag< float > foo(5.6); // forward ctor
myTag< float > bar(foo); // forward ctor
return 0;
}
但是,我无法正确声明和定义复制/移动构造函数。我声明了一个通用构造函数重载模板,该模板将其参数转发给底层类型,只要这种构造是可能的。但是,由于隐式转换运算符,它会捕获 myTag 的每个实例化,从而有效地隐藏复制/移动构造函数。
非默认复制/移动语义的要点是复制/移动 key 值与使用构造函数模板默认初始化它。
如何让编译器优先考虑/优先考虑显式复制/移动构造函数与泛型重载?是否有任何其他 SFINAE 检查替代 is_constructible<> 可以避免隐式转换?
编辑:我应该补充一点,我正在寻找 C++14 解决方案。
【问题讨论】:
-
这需要一个比我更好的语言律师(建议你添加那个标签)来告诉你为什么,但你也可以通过添加
myTag(myTag& other)重载来解决它。问题是 foo 不是myTag&&,也不是const myTag&,所以编译器选择通过operator T&转换并使用第一个构造函数。 -
如果只有 1 个参数,并且该参数是
myTag(从类型中删除 cv 和引用),您还可以扩展enable_if以禁止此重载。 -
@DaveS 是的,可以。我只是不明白为什么
foo不是const myTag&,或者更确切地说,为什么const myTag&不绑定到它而不是编译器寻找替代重载。 -
@joaocandre:我认为您感到困惑的一点是
myTag&在重载选择期间未绑定到const myTag&。在重载选择期间,它首先寻找完全匹配,以及需要某种转换的匹配(例如myTag&到const myTag&)。因此,由于您的转发构造函数不需要转换,除非其他重载之一完全匹配,否则它将被选中。 -
@DaveS 但这正是我的观点 - 前向构造函数 应该 需要转换,因为
float不能从myTag参数的任何变体中构造 - 就我而言知道它正在调用隐式转换重载operator T&并复制到新实例中。对于myTag&、const myTag&或类似的,SFINAE 检查应该(理想情况下)失败,因此应该调用复制/移动构造函数。
标签: c++ templates variadic-templates sfinae constructor-overloading