Search code examples
c++templatesc++17template-meta-programmingtype-traits

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


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


Solution

  • 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 ...