模板参数typename与非typenames之间的区别?

时间:2016-05-09 11:17:09

标签: c++ templates c++11

代码示例1:

namespace detail {
    enum enabler { dummy };
}

class foo {
public:
    template <typename T,
              typename std::enable_if<!std::is_integral<T>::value,
                                      detail::enabler>::type = detail::enabler::dummy>
    void func(T t) {
        std::cout << "other" << std::endl;
    }

    template <typename T,
              typename std::enable_if<std::is_integral<T>::value,
                                      detail::enabler>::type = detail::enabler::dummy>
    void func(T t) {
        std::cout << "integral" << std::endl;
    }
};

代码示例2:

namespace detail {
    enum enabler { dummy };
}

class foo {
public:
    template <typename T,
              typename T2 = typename std::enable_if<!std::is_integral<T>::value, detail::enabler>::type>
    void func(T t) {
        std::cout << "other" << std::endl;
    }

    template <typename T,
              typename T2 = typename std::enable_if<std::is_integral<T>::value, detail::enabler>::type>
    void func(T t) {
        std::cout << "integral" << std::endl;
    }
};

我理解为什么样本2不能编译。基本上因为两个模板函数彼此相似(它们甚至具有相同的模板参数TT2)。

我不明白为什么样本1编译!我发现这是同样的问题。基本上,不是将第二个模板参数作为typename,而是这次enum而不是typename

有人可以解释一下吗?

此外,我执行了以下代码,它返回true,这更令人困惑! (理所当然,但是样本1编译会让人感到困惑)

std::cout
    << std::boolalpha
    << std::is_same<std::enable_if<std::is_integral<int>::value, int>::type,
                    std::enable_if<!std::is_integral<float>::value, int>::type>::value
    << std::endl;

2 个答案:

答案 0 :(得分:8)

默认参数(无论是默认函数参数还是默认模板参数)都不是函数签名的一部分。您只能使用给定签名定义一个函数。

在代码示例1中,如果我们删除参数的名称和所有默认值,我们有两个函数:

template <typename T, std::enable_if_t<!std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }

template <typename T, std::enable_if_t<std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }

这是两个不同的签名 - 第二个模板参数在两个函数中都有不同的类型 - 因此从这个角度看它是有效的。

现在,当我们实际调用它时会发生什么。如果T是整数类型,则在两种情况下都会替换第二个参数:

template <typename T, ????>                    void func(T ) { ... }
template <typename T, detail::enabler = dummy> void func(T ) { ... }

在第一个函数中,表达式typename std::enable_if<false, detail::enabler>::type是不正确的。该类型没有type typedef。通常,编写不正确的代码是一个硬编译器错误。但是,由于一个名为SFINAE的规则(替换失败不是错误),在模板替换的直接上下文中出现的格式错误的代码不是错误 - 它只会导致从考虑集中删除函数/类模板特化。所以我们最终得到一个有效的:

template <typename T, detail::enabler = dummy> void func(T ) { ... }

被调用。

但是,在代码示例2中,我们有以下两个函数:

template <typename T, typename>
void func(T ) { ... }

template <typename T, typename>
void func(T ) { ... }

那些是一样的!我们两次定义相同的函数 - 这是不允许的,因此错误。出于同样的原因,这是一个错误:

int foo(int x = 1) { return x; }
int foo(int x = 2) { return x; }

是一个错误。

这就是为什么Xeo使用(不可构造的)枚举来启用if - 它使得更容易编写不相交的函数模板特化,因为你可以使用枚举,但是你不能用类型来做。

请注意,您可以类似地使用int

之类的内容
template <class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
void func(T ) { ... }

但是这将允许邪恶的用户为该参数提供一个值(在这个特定的上下文中它不重要,但在其他情况下它可能不重要)。使用detail::enabler时,不能提供此类值。

答案 1 :(得分:2)

我想我得出了一个结论(也许任何人都可以验证或更新):

在样本2中:

当编译器遇到函数调用并尝试将其与模板匹配时,它会找到两个参数为typename Ttypename T2的模板(默认值无关紧要,这是正常的),这会创建含糊不清。

在样本1中:

当编译器发出函数调用时,它会尝试将它与两个模板中的一个匹配,其中一个模板将成功,因此它将具有typename Tdetail::enabler而另一个将没有值为第二个模板参数定义,因此在这种情况下将消除歧义。