GCC:模板构造函数在需要copy-constructor时实例化

Dav*_*iD. 8 c++ gcc copy-constructor

在下面的示例中,GCC >= 4.7实例化模板构造函数(您可以通过读取错误消息来观察),尽管只需要隐式生成的复制构造函数.

#include <type_traits>

// 'ambiguous' is ambiguous for 'ambiguous<int, int>'
template<typename A, typename B> 
struct ambiguous : std::false_type {};

template<typename T> 
struct ambiguous<int, T> : std::true_type {};

template<typename T> 
struct ambiguous<T, int> : std::true_type {};

// quantity
template<typename Type>
class quantity
{
public:
    quantity() = default;

    // Copy-constructor is implicitly created

    // Template constructor
    template<
        typename T,
        typename = typename std::enable_if<ambiguous<Type, T>::value>::type
    >
    quantity(quantity<T>) {}

    template<
        typename T,
        typename = typename std::enable_if<ambiguous<Type, T>::value>::type
    >
    void set(quantity<T>) {}
};

// main
int main()
{   
    quantity<int> a;
    quantity<float> b;
    b.set(a);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码编译GCC < 4.7,clang并且MSVS(不知道哪个版本,我使用了http://rextester.com/runco​​de中的那个版本).在GCC >= 4.7编译失败时出现以下消息:

main.cpp: In substitution of ‘template<class T, class> quantity<Type>::quantity(quantity<T>) [with T = int; <template-parameter-1-2> = <missing>]’:
main.cpp:39:12:   required from here
main.cpp:23:9: error: ambiguous class template instantiation for ‘struct ambiguous<int, int>’
         typename = typename std::enable_if<ambiguous<Type, T>::value>::type
         ^
main.cpp:9:8: error: candidates are: struct ambiguous<int, T>
 struct ambiguous<int, T> : std::true_type {};
        ^
main.cpp:12:8: error:                 struct ambiguous<T, int>
 struct ambiguous<T, int> : std::true_type {};
        ^
main.cpp: In function ‘int main()’:
main.cpp:31:10: error:   initializing argument 1 of ‘void quantity<Type>::set(quantity<T>) [with T = int; <template-parameter-2-2> = void; Type = float]’
     void set(quantity<T>) {}
Run Code Online (Sandbox Code Playgroud)

因此,在调用时b.set(a);,GCC显然会查找一个复制构造函数,并在实例化模板构造函数的同时实例化ambiguous<int, int>哪个(嗯......)不明确.

问题:GCC即使需要复制构造函数,也可以实例化模板构造函数吗?

eca*_*mur 6

gcc是对的.

这里有几个问题,不幸的是你的问题已经混淆了:

首先,gcc <4.7的行为并没有根本不同; 所有版本的gcc自(至少)4.4拒绝非常相似的程序:

struct S;

template<typename, typename> struct U {};
template<typename T> struct U<S, T> {};
template<typename T> struct U<T, S> {};

struct S {
  S() = default;
  template<typename T, typename = typename U<S, T>::type> S(T) {}
};

int main() {   
  S a;
  S b(a);
}
Run Code Online (Sandbox Code Playgroud)

请注意,唯一真正的区别是复制初始化是显式的,而不是包含在函数调用中.顺便说一句,Clang接受这个程序.

接下来,涉及复制构造函数的这个问题并不是基本的(C++ 11中的规则12.8p6); 这是另一个类似的程序,gcc(所有版本)拒绝和clang接受:

struct S {};

template<typename, typename> struct U {};
template<typename T> struct U<S, T> {};
template<typename T> struct U<T, S> {};

void f(S);
template<typename T> typename U<S, T>::type f(T);

int main() {   
  S a;
  f(a);
}
Run Code Online (Sandbox Code Playgroud)

clang和gcc之间的区别在于14.8.2p8的应用:

[...] [注意:对替换类型和表达式的评估可能会导致副作用,例如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等.这样的副作用是不在"直接背景"中,可能导致程序格式不正确. - 结束说明]

模板专业化的模糊性ambiguous<int, int>超出了直接的背景,因此程序是不正确的.(对此的一个支持性论点是模板特化歧义不会出现在后续类型推导失败原因列表中).

MSVC再次与众不同; 它接受以下程序,clang和gcc都拒绝:

template<typename T> struct U { typedef typename T::type type; };
struct S {
    S() = default;
    template<typename T, typename = typename U<T>::type> S(T) {}
};

int main() {
    S a;
    S b(a);
}
Run Code Online (Sandbox Code Playgroud)

这是规则12.8p6:

如果类X的构造函数的第一个参数是类型(可选地是cv-qualified)X并且没有其他参数或者所有其他参数都具有默认参数,那么它的构造函数的声明是错误的.从不实例化成员函数模板以生成此类构造函数签名.

但是,为了确定成员函数模板实例化是否是关于12.8p6格式不正确的构造函数,有必要实例化其声明(参见14.7.1p9).请注意,MSVC拒绝以下程序,因此它甚至不一致:

template<typename T> struct U { typedef typename T::type type; };
struct S {
    S() = default;
    template<typename T> S(T, typename U<T>::type *p = 0) {}
};

int main() {
    S a;
    S b(a);
}
Run Code Online (Sandbox Code Playgroud)

这有一些非常有趣的行为效果; MSVC接受以下(格式错误)的程序:

template<typename T> struct U { typedef typename T::type type; };
struct S {
    S() = default;
    template<typename T, typename = typename U<T>::type> S(T) {}
};
template<typename T> typename U<T>::type f(T) { return 0; }

int main() {
    S a;
    S b(a);  // XXX
    f(a);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果复制初始化S b(a)被注释掉,程序将被拒绝!