【问题标题】:Why does C++'s `variable template` not behave as expected?为什么 C++ 的“变量模板”的行为不符合预期?
【发布时间】:2018-12-16 09:34:00
【问题描述】:
#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 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)]

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

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

然后一切正常。

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

【问题讨论】:

  • 不确定为什么会出现编译器错误;我正在使用 Visual Studio 2017 CE 版本 15.8.6,我的编译器语言标准设置为 ISO C++ Latest Draft Standard (/std:c++latest),它编译和构建都很好,我什至在 main 内部调用了函数 f();,它仍然编译并运行并顺利退出。
  • 我的编译器在 windows 上是 clang 7.0
  • 可能在编译器如何解释它...
  • IDK 是你故意写这段代码来测试编译器的能力还是有一些你没有注意到的事实:你模板A(T&amp;&amp;)一个复制构造函数,它需要参与确定T obj(std::declval&lt;Args&gt;()...);std::is_copy_constructible 中的格式是否正确。换句话说,std::is_copy_constructible 依赖于它自己。所以实际上会出现一些奇怪的行为。至于为什么第二种方法似乎“有效”,那可能是一些 SIFAE 的东西打破了循环依赖,并丢弃了这个模板。
  • 见这里:wandbox.org/permlink/T6E5GveUuBNVQbCZ,即使::value 成员在std::is_copy_constructible 中也消失了

标签: c++ templates c++17 template-meta-programming typetraits


【解决方案1】:

std::is_copy_constructible_v&lt;A&gt; 被实例化的时候,即在isCc 的定义之后,A 不完整,而std::is_copy_constructible_v 要求它的模板参数是完整的。

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

在没有isCc的版本中,即使在std::is_copy_constructible_v&lt;A&gt;被实例化的时候,A也是完整的1,所以所有的编译器都会欣然接受代码。


1标准中的相关规则:

[class.member]/6

一个类的完整类上下文是一个

  • 函数体,
  • 默认参数,
  • noexcept 说明符([except.spec]),
  • 合同条件,或
  • 默认成员初始化器

在类的成员规范中...

[class.member]/7

...该类在其完整类上下文中被认为是完整的...

【讨论】:

    【解决方案2】:

    我已在 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 模式下构建& rand 代码。

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

    然后他在 cmets 中回复:

    我的编译器是 windows 上的 clang 7.0

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

    如果可以的话,可以尝试用不同的编译器编译你最初的尝试,尝试GCCClang 的不同版本,看看你是否得到不同的结果。

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

    【讨论】:

      【解决方案3】:

      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 应为完整类型、cv void 或未知边界数组。

      当 T 为 A 时,template&lt;typename T, bool = isCc&lt;T&gt;&gt; 不是这样

      【讨论】:

      • 我是用c++2a编译的。
      • 我修改了问题的标题和标签
      • 这个解决方案改变了程序的含义。 OP想要同时删除参考和简历。我更改了问题,使其成为没有实验性位的纯 C++17,但仍然无法编译。
      猜你喜欢
      • 2021-04-09
      • 2021-10-17
      • 2012-12-07
      • 1970-01-01
      相关资源
      最近更新 更多