C++ 重载的非类型模板不明确,而非模板化函数正常

C++ 重载的非类型模板不明确,而非模板化函数正常,c++,templates,non-type,C++,Templates,Non Type,如果我们有一个模板函数,它接受类型为int或short的非类型参数,编译器会抱怨以下调用的不确定性: // Definition template <int I> void foo() { std::cout << "I: " << I << '\n'; } template <short S> void foo() { std::cout << "S: " << S << '\n'; } //

如果我们有一个模板函数,它接受类型为
int
short
的非类型参数,编译器会抱怨以下调用的不确定性:

// Definition
template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }

// Usage
foo<0>(); // Ambiguous, int or short?
foo
的调用是明确的!它接受
int
重载(即使模板版本没有)。嗯,想一想,这并不是一个令人惊讶的行为,毕竟没有办法指定
short
文本,因此编译器认为
0
int
(这是默认行为AFAIK),为了明确地调用非模板化的
foo
版本的
short
,我们可以显式地实例化
short

foo(0);        // "i: 0"
foo(short{0}); // "s: 0"
因此,我认为这将明确模板化版本,但事实并非如此:

foo<int{0}>();   // Ambiguous call, candidates are int and short versions
foo<short{0}>(); // Ambiguous call, candidates are int and short versions
foo();//调用不明确,候选是int和short版本
foo();//调用不明确,候选是int和short版本
重载“foo()”的调用不明确
foo();
注:候选人包括:
void foo()[带int I=0]
void foo()[具有短int S=0]
重载“foo()”的调用不明确
foo();
注:候选人包括:
void foo()[带int I=0]
void foo()[具有短int S=0]
我最后尝试的是使用实例而不是文字:

template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }

void foo(int i)   { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }

constexpr int i{1};
constexpr short s{5};

int main()
{
    foo(i); // "i: 1"
    foo(s); // "s: 5"
    foo<i>(); // Ambiguous! (expected "I: 1")
    foo<s>(); // Ambiguous! (expected "S: 5")
    return 0;
}

template void foo(){std::cout这里的模板函数是按值参数化的,并且只按值参数化,不按类型参数化!更新:现在我不确定。
另一方面,未模板化的版本是按类型参数化的(人们可以享受多态调用)

更新:
好的,看起来实例化的函数名称被破坏实际上取决于数值模板参数的类型。

编译器错误消息是错误的*。你会直觉地认为这意味着“函数调用是模糊的!”,但实际上,编译在早期阶段失败,甚至在那时还没有生成专用函数的定义

它真正的意思是:“功能专门化是模糊的!”

让我们看看它是如何编译的:

template <short S> void foo() { std::cout << "S: " << S << '\n'; }

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}
-[结束示例]



*错误消息完全正确。可能不是特别直观,但它反映了标准中指定的过程。-T.C.

以下是编写
f()
时发生的情况

  • 编译器查找
    f
    ,并找到两个函数模板声明:

    template <int I>   void foo();
    template <short S> void foo();
    
    模板void foo();
    模板void foo();
    
  • 编译器会看到显式模板参数列表,并尝试将其替换到每个函数模板声明中:

    template <short S> void foo() { std::cout << "S: " << S << '\n'; }
    template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
    
    template<>
    void foo<0>();
    
    int main(int argc, char* argv[])
    {
        foo<0>();
        return 0;
    }
    
    template <int I>   void foo(); // with I = 0
    template <short S> void foo(); // with S = 0
    
    模板void foo();//I=0
    模板void foo();//带S=0
    
    在这两种情况下,替换都会成功,因为
    0
    是一个
    int
    ,可以转换为
    short
    ,并且在这种情况下,转换是允许的转换

  • 替换后,将生成两个候选函数专门化。这两个函数都是可行的。然后执行重载解析-由于签名相同且没有适用的分界线,重载解析将失败,调用不明确


  • 这里的要点是,正常的重载解析规则不适用于模板参数。模板参数的转换是在常规重载解析发生之前的早期阶段应用的。

    插入代码会得到一些不错的错误消息。我对它们的解释是使用
    foo(i)时
    编译器使用
    i
    类型进行模板推导。使用
    foo()时
    编译器不再使用
    i
    的类型,而是使用它的值,因为模板是
    。因为
    i
    的值可以满足两个模板的要求,所以会出现歧义错误。事实上,这是因为现在该值成为
    f()类型的一部分
    ;您要求编译器区分
    foo()
    foo()
    (其中一个是
    int
    类型的零,另一个是
    short
    类型的零)。@NathanOliver如您所见,错误消息确实是我粘贴在问题上的消息(事实上,我已经使用ideone测试了所有案例)因此,如果错误消息很好,我无法理解它为什么会失败。那么,显式指定类型
    int
    short
    ,在这种情况下为什么不明确?@PaperBirdMaster:之所以不明确,是因为它使用的是提供的参数的值,而不是类型。请检查我明显是int值的位置。What正在发生类似于无法仅基于返回类型重载函数。编译器如何知道您要使用什么?这是一个我想相信的解释,但我认为它不能解释为什么显式值
    foo()
    仍然不明确。@PaperBirdMaster既
    foo()
    foo()
    转换为
    模板void foo();
    。我已经相应地更新了我的答案。很抱歉再次打扰您,Gábor,但是从
    constepr int I{1}
    constepr short s{5}
    用作
    foo()
    foo()时推断出的显式类型如何
    ?在这种情况下,为什么现有和明确定义的项的类型不能消除模板的歧义?并且错误消息完全正确。可能不是特别直观,但它反映了标准中指定的过程。此外,
    template void foo();
    是完全有效的。但是,使用显式整数和显式short(
    consteprinti{1};
    consteprshort s{5};
    foo();
    foo();
    )没有歧义,因为它们是实例(没有文字)而且没有转换。@PaperBirdMaster这没关系。转换发生在步骤2中,替换仍然成功
    template <short S> void foo() { std::cout << "S: " << S << '\n'; }
    
    template<>
    void foo<0>();
    
    int main(int argc, char* argv[])
    {
        foo<0>();
        return 0;
    }
    
    template<>
    void foo<0>(){
        std::cout << "S: " << 0 << '\n';
    }
    
    template <short S> void foo() { std::cout << "S: " << S << '\n'; }
    template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
    
    int main(int argc, char* argv[])
    {
        foo<0>();
        return 0;
    }
    
    template <short S> void foo() { std::cout << "S: " << S << '\n'; }
    template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
    
    template<>
    void foo<0>();
    
    int main(int argc, char* argv[])
    {
        foo<0>();
        return 0;
    }
    
    template <short S> void foo() { std::cout << "S: " << S << '\n'; }
    template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
    
    template<>
    void foo<0>();
    
    int main(int argc, char* argv[])
    {
        return 0;
    }
    
     template <int> int f(int);
     template <signed char> int f(int);
     int i1 = f<1000>(0); // OK
     int i2 = f<1>(0); // ambiguous; not narrowing
    
    template <int I>   void foo();
    template <short S> void foo();
    
    template <int I>   void foo(); // with I = 0
    template <short S> void foo(); // with S = 0