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

时间:2014-05-19 15:23:14

标签: 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);
}

以上代码汇编为GCC < 4.7clangMSVS(不知道哪个版本,我使用的是http://rextester.com/runcode中的版本)。在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>) {}

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

问题:即使需要复制构造函数,GCC是否有权实例化模板构造函数?

1 个答案:

答案 0 :(得分:6)

gcc是对的。

这里有一些问题很遗憾在你的问题中已经混淆了:

首先,gcc的行为&lt; 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);
}

请注意,唯一真正的区别是复制初始化是显式的,而不是包含在函数调用中。顺便说一句,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);
}

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

这取决于规则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);
}

这有一些非常有趣的行为效果; 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);
}

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