模板参数typename和非typename之间的区别

difference between template parameters typename vs non typenames?

本文关键字:typename 区别 之间 和非 参数      更新时间:2023-10-16

代码示例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;

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

在代码示例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,不能提供这样的值。

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

样品2:

当编译器遇到函数调用并试图将其与模板匹配时,它会找到两个参数为typename Ttypename T2的模板(默认值无关紧要,这是正常的),这会产生歧义。

样品1:

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