为什么 C++ 的“变量模板”没有按预期运行?

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()
{}

错误信息:

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 Studio17\Community\VC\Tools\MSVC.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)]

但是,如果我将 class A 更改如下:

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

那一切就ok了

为什么 C++ 的 variable template 没有按预期运行?

is_copy_constructible_v 已在 C++17 中添加,而 std::remove_cvref 将在 C++20 中添加。 C++20 支持仍处于实验阶段。

编写正确的 C++14 代码将解决问题:

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

或 C++17:

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

下一个问题的答案:

std::is_copy_constructible模板参数要求为

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

当 T 为 A

时,template<typename T, bool = isCc<T>> 不是

我在 Visual Studio 2017 CE 版本 15.8.6 中成功编译了 OP's 原始提议代码,我的编译器语言标准在我的 IDE's 设置中设置为 ISO C++ Latest Draft Standard (/std:c++latest)我的机器是 运行 Windows 7 64bit Ultimate。我在 Debug - x86 模式下构建了 & 运行d 代码。

我什至走得更远,在 main 中调用了他的函数 f(),它仍然构建、编译、运行 并没有错误地退出。

然后他在评论中回复:

My compiler is clang 7.0 on windows

我不知道这是 Clang's 编译器中的错误,还是 Clang 只是对它的解释不同。

如果可以的话,也许可以尝试使用不同的编译器编译您最初的尝试,尝试 GCC 或不同版本的 Clang,看看是否会得到不同的结果。

这很有趣,我认为需要进一步调查以确定它是否与 Clang's 编译器有关。

std::is_copy_constructible_v<A> 被实例化时,即在 isCc 的定义之后,A 尚未完成,而 std::is_copy_constructible_v 需要其模板参数完成。

此代码是否应该工作仍然是一个起草问题:Core Language Issue 287,因此一些编译器接受您的代码而其他编译器拒绝它是合理的。

在没有isCc的版本中,即使在实例化std::is_copy_constructible_v<A>的时候,A也是完整的1,所以所有的编译器愉快地接受代码。


1 标准中的相关规则:

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