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

3

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

Gravelly answered 16/12, 2018 at 9:34 Comment(5)
Not sure why you are getting compiler error; I'm using Visual Studio 2017 CE version 15.8.6 and I have my compiler language standard set to ISO C++ Latest Draft Standard (/std:c++latest) and it compiles and builds fine, also I even called the function f(); inside of main and it still built compiled and ran and exited without errors.Dogvane
My compiler is clang 7.0 on windowsGravelly
Could be in how the compiler interprets it...Dogvane
IDK whether you write this code deliberately to test compiler's ability or there are some facts you didn't notice: You template A(T&&) is a copy constructor, it will take part in the determination of whether T obj(std::declval<Args>()...); is well formed in std::is_copy_constructible. In other words, std::is_copy_constructible depends on itself. So some strange behavior is actually expected. As for why the second approach will seems to "work", that might be some SIFAE stuff breaks the circular dependency, and drop this template.Heartbroken
See here: wandbox.org/permlink/T6E5GveUuBNVQbCZ, even the ::value member disappears in std::is_copy_constructibleHeartbroken
P
5

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

Parfait answered 16/12, 2018 at 13:18 Comment(0)
D
2

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.

Dogvane answered 16/12, 2018 at 10:43 Comment(0)
A
1

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

Adin answered 16/12, 2018 at 9:40 Comment(3)
I compiled it with c++2a.Gravelly
I revised the question title and tagsGravelly
This solution changes the meaning of the program. OP wants to remove both reference and cv. I changed the question so that it is plain C++17 without experimental bits, but it still fails to compile.Overprint

© 2022 - 2024 — McMap. All rights reserved.