重载的非类型模板是不明确的,而非模板化的函数是可以的

时间:2015-03-05 10:58:00

标签: c++ templates non-type

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

// 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?

起初我对此行为并不感到惊讶,文字0可能是intshort,但如果我们尝试这样做:

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

// Usage
foo(0); // "i: 0"!

foo的调用毫不含糊!它需要int重载(即使模板版本没有)。好了,经过一番思考后,这并不是一个令人惊讶的行为,毕竟没有办法指定short字面值,所以编译器认为0int (这是默认行为AFAIK),为了明确地调用非模板short的{​​{1}}版本,我们可以显式实例化foo

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

我尝试的最后一件事是使用实例而不是文字:

call of overloaded 'foo()' is ambiguous
 foo<int{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]

call of overloaded 'foo()' is ambiguous
 foo<short{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short 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; } 的调用含糊不清? (请注意,无模板foo需要foo版本,因此是明确的。)
  • 为什么即使在指定呼叫类型后,对模板化int的调用仍然不明确? (请注意,无模板foo 正常工作)。

感谢。

3 个答案:

答案 0 :(得分:5)

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

它的真正含义是:“功能专业化是暧昧的!”

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

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

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

编译的第一步是模板专业化。

第1步:编译器意识到foo<0>是模板特化,并相应地生成函数声明:

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

template<>
void foo<0>();

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

第2步:编译器重新调用该函数实际被调用(在这种情况下这似乎很明显,但是当你有一个类模板时它就不那么明显了。),并生成一个定义: / p>

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';
}

第3步:现在你有一个可调用的函数,编译继续正常进行。

让我们尝试按照您的情况执行相同的步骤:

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;
}

第1步:生成函数声明:

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;
}

此时编译失败,因为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[])
{
    return 0;
}

如果没有函数调用,您将收到相同的错误消息!

<强>更新

所以,重要的是所有转换为专门的函数声明。因此,无论您编写foo<int{0}> foo<short{0}>,编译器都会为两者生成template<> void foo<0>();。显式类型将被忽略。 (这就是为什么它们非常重要constexpr - s。)

<强>更新 作为T.C.在他的评论中指出,在standard(PDF中的第413页)中有一个非常相似的例子:

  

[示例:在以下示例中,假设签名的char不能   代表值1000,缩小转换率(8.5.4)   需要将int类型的template-argument转换为signed char,   因此第二个模板的替换失败(14.3.2)。

 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
     

-end example]


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

答案 1 :(得分:5)

在这里写下f<0>()时会发生什么。

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

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

    template <int I>   void foo(); // with I = 0
    template <short S> void foo(); // with S = 0
    

    在这两种情况下,替换成功,因为0int,可以转换为short,转换是在此上下文中允许的转化。

  3. 替换后,生成两个候选函数专精。两者都是可行的。然后执行过载分辨率 - 由于签名相同且没有仲裁器适用,因此重载解析失败并且调用不明确。

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

答案 2 :(得分:0)

此处的模板函数按值进行参数化,并且仅按值而非按类型进行参数化! 更新:现在我不确定。 另一方面,没有模板化版本按类型进行参数化(并且可以享受多态调用)。

<强>更新 好吧,看起来像实例化的函数损坏名称实际上取决于数值模板参数的类型。