Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/templates/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 基于类的特征禁用类构造函数的正确方法_C++_Templates_Sfinae_Enable If - Fatal编程技术网

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。感谢您的链接!很高兴知道标准中正确地解决了这个问题。虽然,带有附加模板参数的解决方案更简单,所以我将其标记为已接受。