Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 733
xmllmx Avatar asked Dec 16 '18 09:12

xmllmx


3 Answers

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

like image 72
xskxzr Avatar answered Oct 17 '22 16:10

xskxzr


I had successfully compiled the OP's original proposed code in Visual Studio 2017 CE version 15.8.6 with my compiler's language standard set to ISO C++ Latest Draft Standard (/std:c++latest) in my IDE's settings and my machine is running Windows 7 64bit Ultimate. I built & rand the code in Debug - x86 mode.

I even went as far and made a call to his function f() within main, and it still built, compiled, ran and exited without error.

He then replied back in the comments with:

My compiler is clang 7.0 on windows

I don't know if this is either a bug in Clang's compiler or if Clang just interprets it differently.

Maybe try compiling your original attempt with different compilers if you are able to, try GCC or a different version of Clang and see if you get different results.

This is something interesting that I believe needs further investigation to determine if it has to do with Clang's compiler specifically or not.

like image 35
Francis Cugler Avatar answered Oct 17 '22 17:10

Francis Cugler


is_copy_constructible_v has been added in C++17 while std::remove_cvref is to be added in C++20. C++20 support is still experimental.

Writing proper C++14 code will solve the problem:

template<typename T>
constexpr bool isCc = std::is_copy_constructible<std::remove_reference_t<::std::remove_cv_t<T>>>::value;

or C++17:

template<typename T>
constexpr bool isCc = std::is_copy_constructible_v<std::remove_reference_t<::std::remove_cv_t<T>>>;

Answer to next question:

std::is_copy_constructible template parameter requirements are

T shall be a complete type, cv void, or an array of unknown bound.

which is not in case of template<typename T, bool = isCc<T>> when T is A

like image 1
user7860670 Avatar answered Oct 17 '22 18:10

user7860670