使用 enable_if 选择类构造函数

68
考虑以下代码:
#include <iostream>
#include <type_traits>

template <typename T>
struct A {
    int val = 0;

    template <class = typename std::enable_if<T::value>::type>
    A(int n) : val(n) {};
    A(...) { }

    /* ... */
};

struct YES { constexpr static bool value = true; };
struct NO { constexpr static bool value = false; };

int main() {
    A<YES> y(10);
    A<NO> n;
    std::cout << "YES: " << y.val << std::endl
              << "NO:  " << n.val << std::endl;
}

我想使用enable_if来有选择地定义构造函数A::A(int),只针对某些类型。对于所有其他类型,应该使用默认构造函数A::A(...),这是编译器在替换失败时的默认情况。然而,对我来说,这在编译器(gcc版本4.9.0 20130714)中仍然有意义,因为它仍然在抱怨。
类似这样的构造函数是否可能?这是否可能使用其他构造函数(拷贝构造函数和移动构造函数)?

可能是一个辅助工厂类? - πάντα ῥεῖ
这个问题的关闭正在Meta上进行讨论 cc @JanSchultke - undefined
3个回答

58

我认为使用单个默认的模板参数无法实现此功能,因为它的值需要在类模板实例化时解析。

我们需要将替换推迟到构造函数模板实例化的点。一种方法是将模板参数默认为T,并向构造函数添加额外的虚拟参数:

template<typename U = T>
A(int n, typename std::enable_if<U::value>::type* = 0) : val(n) { }

@JoelFalcou,你的确是对的。这个还不行 - Rapptz
@Rapptz 抱歉,这是因为它被用在默认模板参数中。它需要在构造函数的签名中。我会修复它。 - jrok
4
@jrok说,通常把它放在模板参数中更常见,但是你说得对,用单个模板参数不可能实现这一点(这实际上就是问题的答案)。 - Rapptz
3
我也希望可以為您做中文翻譯。我將優化翻譯質量,使其更易懂,但不會改變原意。請勿要求解釋或返回除翻譯結果以外的內容。以下是需要翻譯的內容:@bb94 我也是這樣,所以我在gcc 9.3.0 (mingw)上進行了測試。如果沒有使用-O選項,則不會進行優化。使用-O1選項時,會將其刪除,並且內聯整個構造函數。(基於觀察反編譯二進制文件)。 - memtha
1
这真是天才,谢谢! 我在没有额外参数的情况下使用以下混合模板: template <typename U = T, class = typename std::enable_if<U::value>::type> - Kikaxa
显示剩余5条评论

25

使用C++20

你只需在构造函数中添加requires即可实现这一点:

A(int n) requires T::value : val(n) { }
requires子句接收一个常量表达式,该表达式的值为truefalse,从而决定是否在重载解析中考虑此方法。如果requires子句为真,则考虑该方法;否则忽略它。
代码:https://godbolt.org/z/948z41zKK

3
我选择这个作为答案,因为它似乎是最符合最新的C++标准的。对于需要向后兼容的人,我仍然建议使用@jrok的答案,因为它也能很好地工作。 - tomas789

14
通常使用匿名默认参数来完成此操作:
A(int n, typename std::enable_if<T::value>::type* = 0) : val(n) {};

您不能使用类中的模板参数来SFINAE出方法。

因此,一种方法是添加一个用于替代int的虚拟类型:

请参见:http://ideone.com/2Gnyzj

#include <iostream>
#include <type_traits>

template <typename T>
struct A {
    int val = 0;

    template<typename Integer
            ,typename  = typename std::enable_if<T::value && sizeof(Integer)>::type
            >
    A(Integer n) : val(n) {};

    A(...) {}
    /* ... */
};

struct YES { constexpr static bool value = true; };
struct NO { constexpr static bool value = false; };

int main() {
    A<YES> y(10);
    A<NO> n;
    std::cout << "YES: " << y.val << std::endl
              << "NO:  " << n.val << std::endl;
}

这是因为您使用成员模板参数来SFINAE掉构造函数,但测试始终为真,因此不会污染您的检查。

3
仍然会出现相同的错误,我曾认为原帖的方法是最常见的。 - chris
@chris 这确实是这样。 - Rapptz
它是使用C++11编写的。我刚刚注意到了std::enable_if。 - Joel Falcou
这个解决方案完全有效。小缺陷是它改变了函数的签名。是否有一个具有相同签名的解决方案?就像@jrok的那样? - tomas789
3
你可以使用另一个类型特性,具体见此链接:http://ideone.com/fdo7JO。 - Rapptz
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接