为什么 enable_if 的模板构造函数中的可选参数可以帮助编译器推导出模板参数?

Why does an optional argument in a template constructor for enable_if help the compiler to deduce the template parameter?

最小的例子很短:

#include <iostream>
#include <array>
#include <type_traits>

struct Foo{
    //template <class C>
    //Foo(C col, typename std::enable_if<true,C>::type* = 0){
    //    std::cout << "optional argument constructor works" << std::endl;
    //}
    template <class C>
    Foo(typename std::enable_if<true, C>::type col){
        std::cout << "no optional argument constructor works NOT" << std::endl;
    }
};

int main()
{
    auto foo = Foo(std::array<bool,3>{0,0,1});
}

第一个构造函数按预期工作。但是第二个构造函数没有编译,我得到

error: no matching function for call to ‘Foo::Foo(std::array)’

然而给出的解释

note: template argument deduction/substitution failed

没有帮助,因为 std::enable_if<true, C>::type 应该是 C 这样两个构造函数中的第一个参数在编译器看来应该完全相同。我显然错过了一些东西。为什么编译器的行为不同,对于不使用可选参数的构造函数和 enable_if 是否有任何其他解决方案?

完整的错误信息:

main.cpp:18:45: error: no matching function for call to ‘Foo::Foo(std::array)’
   18 |     auto foo = Foo(std::array<bool,3>{0,0,1});
      |                                             ^
main.cpp:11:5: note: candidate: ‘template Foo::Foo(typename std::enable_if::type)’
   11 |     Foo(typename std::enable_if<true, C>::type col){
      |     ^~~
main.cpp:11:5: note:   template argument deduction/substitution failed:
main.cpp:18:45: note:   couldn’t deduce template parameter ‘C’
   18 |     auto foo = Foo(std::array<bool,3>{0,0,1});
      |                                             ^
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(const Foo&)’
    5 | struct Foo{
      |        ^~~
main.cpp:5:8: note:   no known conversion for argument 1 from ‘std::array’ to ‘const Foo&’
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(Foo&&)’
main.cpp:5:8: note:   no known conversion for argument 1 from ‘std::array’ to ‘Foo&&’

模板参数推导失败,因为 C 出现在 Non-deduced context 中。链接页面列表

The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id

作为 non-deduced 上下文。

他们还进一步提到了另一个例子:

For example, in A<T>::B<T2>, T is non-deduced because of rule #1 (nested name specifier), and T2 is non-deduced because it is part of the same type name, but in void(*f)(typename A<T>::B, A<T>), the T in A<T>::B is non-deduced (because of the same rule), while the T in A<T> is deduced.

模板参数推导不能这样工作。

假设您有一个模板和一个使用该模板的类型别名的函数:

template <typename T>
struct foo;

template <typename S>
void bar(foo<S>::type x) {}

当您调用该函数时,例如 foo(1),那么编译器将不会尝试 foo 的所有实例化以查看是否有一个 type 与 [=15] 的类型相匹配=].它不能这样做,因为 foo::type 不一定是明确的。可能是不同的实例化具有相同的 foo<T>::type:

template <>
struct foo<int> { using type = int; };

template <>
struct foo<double> { using type = int; };

而不是尝试这条路线并可能导致歧义,foo<S>::type x 是一个非推导的上下文。有关详细信息,请参阅 What is a nondeduced context?

其他答案已经解释了为什么参数推导在这里不起作用。如果你想 enable_if 你的构造函数,你可以简单地将条件放在模板列表中,如下所示:

struct Foo{
    //                      your condition here ---v
    template <class C, typename std::enable_if_t< true >* = nullptr>
    Foo(C col) {
        std::cout << "constructor" << std::endl;
    }
};