我想sfinae禁用类模板构造函数-但问题是启用-禁用条件是该类的一个特征。特别是,我希望只有在类中存在匹配构造函数模板参数的特定类方法时才启用该构造函数。
一个简化的例子:
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));
}
};
该解决方案在GCC中工作,据称在MSVC中工作,但在Clang中不起作用。在我看来Clang在这里是正确的,因为在类定义中还没有定义类,所以std::declval<S>()
不应该在这里工作。
所以我的想法是将构造函数定义移出类定义:
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));
}
但这在GCC和MSVC中不起作用。这个解决方案看起来也有些可疑,因为我首先无条件地声明了构造函数,然后才引入SFINAE。
我的问题是——有可能在c++ 11/14/17中解决这个任务吗?一个特定的任务是,只有当类也有匹配的setter方法时,它才应该有构造函数。显然,可以为每个setter方法重载手动定义一个非模板构造函数,但这既繁琐又难以维护。
但我也对更一般的任务感兴趣- sfinae -通过该类的一个trait启用构造函数或其他类方法(需要完整的类定义)。
另外,哪个编译器在我的例子中更正确- 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));
}
生活对于您关于哪个编译器是正确的问题,这是CWG1836
问题不是std::declval
:您可以使用std::declval<S>
与不完整的S
,因为它返回S&&
。问题是当你使用std::declval<S>().set_value
时,因为S
在那一刻还没有完成。
类类型必须是完整的,除非类成员访问出现在该类的定义中。
类成员访问是否出现在S
的定义中。
所以gcc和msvc允许它是正确的。
clang似乎不适用CWG1836的分辨率。在这种情况下,它看到std::declval<S>()
没有依赖类型,因此它可以立即计算成员访问std::declval<S>().set_value
(并且没有这样做)。
为了平息clang,你可以使它成为一个依赖类型。例如,由于您已经在一个模板中,您可以使它依赖于T
:
template<typename T, typename...>
struct dependent_type_identity {
using type = T;
};
template<typename T, typename... Dependence>
using dependent_type_identity_t = typename dependent_type_identity<T, Dependence...>::type;
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<dependent_type_identity_t<S, T>>().set_value(std::declval<T>()))>
S(T && x)
{
set_value(std::forward<T>(x));
}
};
或者做这个答案所做的,并使它依赖于默认为S
的另一个模板参数(它仍然是一个依赖类型,即使没有办法改变它的设置)