19

I recently created this example code to illustrate C++11 variadic template function usage.

template <typename Head, typename... Tail> void foo (Head, Tail...);
template <typename... Tail> void foo (int, Tail...);
void foo () {}

template <typename... Tail>
void foo (int x, Tail... tail)
{
    std :: cout << "int:" << x;
    foo (tail...);
}

template <typename Head, typename... Tail>
void foo (Head x, Tail... tail)
{
    std :: cout << " ?:" << x;
    foo (tail...);
}

foo (int (123), float (123)); // Prints "int:123 ?:123.0"

If the first two lines which forward-declare foo are omitted then this prints int:123int:123 instead. This surprised a certain experienced and knowledgeable C++ programmer.

He was convinced the forward-declarations shouldn't be necessary because the body won't be instantiated until the second phase of two-phase lookup. He thinks the compiler (gcc 4.6) has a bug.

I believe the compiler is right because the two foo are different base template functions and the choice of base template needs to be locked-in during the first phase or else you could violate the one-definition rule by instantiating foo before all versions of it have been defined and then again afterwards (consider how the linker assumes that redundant template function definitions are identical, interchangeable, and discardable).

So, who is right?


The above-linked GOTW nicely explains how and why function templates don't partially specialise, but the existence of variadic template functions seems to add to the confusion -- the intuition that foo<int,Tail...> should be a partial specialisation of foo<Head,Tail...> is stronger than that intuition for non-variadic functions, at least to me.

4

2 回答 2

11

GCC(和 Clang)是对的。MSVC 会出错,因为它没有正确实现查找。

看来,你的同事有误会。查找规则如下:

  • Base 模板函数需要在从定义中调用之前声明
  • 需要在实例化之前声明专用模板函数

注意:这些规则适用于自由函数,在一个类中不需要前向声明

请注意,由于定义也充当声明,因此在您的示例中,没有必要转发声明int版本。

正确的例子:

template <typename T> void foo(T);             // declare foo<T>

template <typename T> void bar(T t) { foo(t); }// call foo<T> (dependent context)

template <> void foo<int>(int);                // declare specialiaztion foo<int>

void bar(int i) { foo(i); }                    // instantiate foo<T> with int
                                               // which is the specialization

如果有可用的基本模板,这是一个错误。如果未在实例化之前声明特化,则不会使用它,这可能意味着违反 ODR 规则(如果另一个实例化使用该特化)。

来自标准(C++0x FDIS):

14.6.4.2

1.对于依赖于模板参数的函数调用,使用通常的查找规则(3.4.1、3.4.2、3.4.3)找到候选函数,除了:

— 对于使用非限定名称查找 (3.4.1) 或限定名称查找 (3.4.3) 的查找部分,仅找到来自模板定义上下文的函数声明。

— 对于使用关联命名空间 (3.4.2) 的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明。

如果函数名称是非限定 ID,并且调用格式错误或找到更好的匹配,则在关联命名空间中的查找考虑所有翻译单元中在这些命名空间中引入的具有外部链接的所有函数声明,而不仅仅是考虑在模板定义和模板实例化上下文中找到的那些声明,则程序具有未定义的行为。

请注意,所提及的段落是针对常规功能的。

于 2011-08-31T11:32:20.323 回答
7

两阶段查找会发现:

  • 在定义点可见的功能,以及
  • ADL 在实例化时可以找到的函数。

template <typename Head, typename... Tail> void foo (Head x, Tail... tail)ADL 找不到,因此如果在定义点不可见,则根本找不到。

换句话说,GCC 是对的。

于 2011-08-31T11:27:23.387 回答