Why does C++'s `variable template` not behave as expected?

  • A+
Category:Languages
#include <type_traits>  template<typename T> struct remove_cvref {     using type = std::remove_cv_t<             std::remove_reference_t<T>>; };  template<typename T> using remove_cvref_t =  typename remove_cvref<T>::type;  template<typename T> constexpr bool isCc = std::is_copy_constructible_v< remove_cvref_t<T>>;  class A final { public:     A() = default;     template<typename T, bool = isCc<T>> // error     A(T&&) {} };  A f() {     A a;     return a; }  int main() {} 

The error message:

error : constexpr variable 'isCc<const A &>' must be initialized by a constant expression 1>main.cpp(14):  note: in instantiation of variable template specialization 'isCc<const A &>' requested here 1>main.cpp(15):  note: in instantiation of default argument for 'A<const A &>' required here 1>C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/include/type_traits(847):  note: while substituting deduced template arguments into function template 'A' [with T = const A &, b1 = (no value)] 1>main.cpp(8):  note: in instantiation of variable template specialization 'std::is_copy_constructible_v<A>' requested here 1>main.cpp(14):  note: in instantiation of variable template specialization 'isCc<A>' requested here 1>main.cpp(15):  note: in instantiation of default argument for 'A<A>' required here 1>main.cpp(21):  note: while substituting deduced template arguments into function template 'A' [with T = A, b1 = (no value)] 

However, if I change the class A as follows:

class A final { public:     A() = default;     template<typename T,      bool = std::is_copy_constructible_v<         remove_cvref_t<T>>> // ok     A(T&&) {} }; 

Then everything is ok.

Why does C++'s variable template not behave as expected?

 


At the point where std::is_copy_constructible_v<A> is being instantiated, i.e. immediately after the definition of isCc, A is not complete, while std::is_copy_constructible_v requires its template argument to be complete.

Whether this code should work is still a drafting issue: Core Language Issue 287, so it is reasonable that some compilers accept your code while others reject it.

In the version without isCc, even at the point std::is_copy_constructible_v<A> is being instantiated, A is complete1, so all compilers happily accept the code.


1 Related rules in the standard:

[class.member]/6

A complete-class context of a class is a

  • function body,
  • default argument,
  • noexcept-specifier ([except.spec]),
  • contract condition, or
  • default member initializer

within the member-specification of the class ...

[class.member]/7

... The class is regarded as complete within its complete-class contexts ...

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: