C++ 基于类的特征禁用类构造函数的正确方法
我想禁用一个类模板构造函数——但关键是启用禁用条件是该类的一个特征。特别是,我只想在特定的类方法(匹配构造函数的模板参数)存在于该类中时启用该构造函数 一个简化的例子:C++ 基于类的特征禁用类构造函数的正确方法,c++,templates,sfinae,enable-if,C++,Templates,Sfinae,Enable If,我想禁用一个类模板构造函数——但关键是启用禁用条件是该类的一个特征。特别是,我只想在特定的类方法(匹配构造函数的模板参数)存在于该类中时启用该构造函数 一个简化的例子: struct S { std::size_t n = 0; void set_value(int i) { n = i; } void set_value(const std::string & s) { n = s.size(); } template <class T, clas
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class = decltype(std::declval<S>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
};
结构
{
标准:尺寸n=0;
无效集_值(int i){n=i;}
void set_值(const std::string&s){n=s.size();}
模板
S(T&x)
{
设置_值(std::forward(x));
}
};
该解决方案在GCC中有效,据称在MSVC中也有效,但在Clang中不起作用。在我看来,Clang在这里是正确的,因为在类定义中,类还没有定义,所以std::declval()
不应该在这里工作
所以我的想法是将构造函数定义移出类定义:
struct S
{
std::size_t n = 0;
void set_value(int i) { n = i; }
void set_value(const std::string & s) { n = s.size(); }
template <class T, class>
S(T &&);
};
template <class T, class = decltype(std::declval<S>().set_value(std::declval<T>()))>
S::S(T && x)
{
set_value(std::forward<T>(x));
}
结构
{
标准:尺寸n=0;
无效集_值(int i){n=i;}
void set_值(const std::string&s){n=s.size();}
模板
S(T&),;
};
模板
S::S(T&&x)
{
设置_值(std::forward(x));
}
但这在GCC和MSVC中不起作用。这个解决方案看起来也有点可疑,因为我首先无条件地声明构造函数,然后才引入SFINAE
我的问题是-是否有可能在C++11/14/17中解决该任务?一个特定的任务是,一个类只有在它也有一个匹配的setter方法时才应该有一个构造函数。显然,可以为每个setter方法重载手动定义一个非模板构造函数,但这既繁琐又难以维护
但我也对更一般的任务感兴趣——SFINAE通过该类的特性(需要完整的类定义)启用构造函数或其他类方法
另外,在我的示例中,哪个编译器更正确-GCC/MSVC还是Clang?您可以引入另一个默认值为
S
的模板参数,并将其用于SFINAE。例如
template <class T, class X = S, class = decltype(std::declval<X>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
模板
S(T&x)
{
设置_值(std::forward(x));
}
关于哪个编译器是正确的问题,这是 问题不在于
std::declval
:您可以对不完整的S
使用std::declval
,因为它返回一个S&
。问题是当您使用std::declval()时。设置值
,因为此时S
还没有完成
但是,有一个明确的例外:
除非类别定义中出现类别成员访问权限,否则类别类型应完整
类成员访问权限确实出现在S
的定义中
所以gcc和msvc允许它是正确的
似乎clang不适用CWG1836的决议。在这种情况下,它看到
std::declval()
没有依赖类型,因此它可以立即计算成员访问std::declval()。set_value
(但没有这样做)
为了安抚clang,可以将其设置为依赖类型。例如,由于您已经在一个模板中,因此可以根据T
是什么来确定它:
模板
依赖结构的类型标识{
使用类型=T;
};
模板
使用dependent\u type\u identity\u t=typename dependent\u type\u identity::type;
结构
{
标准:尺寸n=0;
无效集_值(int i){n=i;}
void set_值(const std::string&s){n=s.size();}
模板
S(T&x)
{
设置_值(std::forward(x));
}
};
或者,执行所做的操作,使其依赖于另一个默认为
S
的模板参数(它仍然是一个依赖类型,即使无法更改它的设置)IIRC,SFINAE不考虑默认模板参数。我错了吗?@DanielLangr默认模板参数和SFINAE的问题是,我们可能不会提供两个仅在默认模板参数上不同的重载;从理论上讲,SFINAE可以做到这一点,但由于默认模板参数不是函数模板签名的一部分,因此程序的格式可能不正确。@dfrib感谢您的澄清,这是有意义的。@DanielLangr。感谢您的链接!很高兴知道标准中正确地解决了这个问题。虽然,带有附加模板参数的解决方案更简单,所以我将其标记为已接受。