如果不使用模板类中的定义,是否应该实例化?

ant*_*_rh 14 c++ gcc templates clang language-lawyer

template <typename T>
struct A
{
    static constexpr T obj {};

    static constexpr bool noexcept_copy = noexcept( T{obj} );
    static void UsesCopy() { T{obj}; }

    static constexpr int  C = 1;
};

struct NoCopy
{
    constexpr NoCopy() = default;
    NoCopy(const NoCopy&) = delete;
};

int main()
{
    return A<NoCopy>::C;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码已由GCC成功编译,但是Clang给出了编译错误:

tmp.cpp:6:57: error: call to deleted constructor of 'NoCopy'
        static constexpr bool noexcept_copy = noexcept( T{obj} );
                                                        ^~~~~~
tmp.cpp:20:16: note: in instantiation of template class 'A<NoCopy>' requested here
        return A<NoCopy>::C;
               ^
tmp.cpp:15:9: note: 'NoCopy' has been explicitly marked deleted here
        NoCopy(const NoCopy&) = delete;
        ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

A::UsesCopy函数也使用复制构造函数,但是编译器不会在此抱怨已删除函数的用法。UsesCopyfunction和noexcept_copyconstexpr有什么区别?两者都使用NoCopy类的副本构造函数,并且都不使用,但是constexpr定义会产生编译错误,而函数定义则不会。

PS。Clang用-std=c++17或编译上面的代码-std=c++2a,但不用-std=c++11或编译-std=c++14

Gus*_*uss 1

我认为处理这个问题的正确方法类似于C++17 之前对静态 constexpr 模板化数据成员的初始化顺序问题的回答中的详细信息中的详细信息。

\n\n

长话短说 - 是的 GCC,没错,Clang 尝试解决复制问题,即使这是不允许的。

\n\n

总结一下:

\n\n

C++14

\n\n

在 p9.4.2.3 - 静态数据成员中,我们有:

\n\n
\n

[...]static可以使用说明符在类定义中声明文字类型的数据成员constexpr;如果是这样,它的声明应指定一个大括号或等于初始化器\n,其中作为赋值表达式的每个初始化器子句都是常量表达式。[...] 如果在程序中使用了 odr,并且命名空间范围定义不应包含初始值设定项,则该成员仍应在命名空间范围中定义\n,则仍应在命名空间范围中定义该成员。

\n
\n\n

因此,数据成员的声明static constexpr只是一个声明,而不是一个定义 - 即使它有一个初始值设定项。需要定义才能进行初始化,而原始 OP 代码中没有提供任何定义。

\n\n

为了阐明这一点,我们有之前引用的 p14.7.1 - 模板 - 隐式实例化(重点是我的):

\n\n
\n

类模板特化的隐式实例化会导致\n 声明的隐式实例化,但不会导致类成员函数、成员类、作用域成员枚举、静态数据成员的定义、默认参数或异常规范的隐式实例化和成员模板

\n
\n\n

GCC 未正确初始化noexcept_copy因为它没有定义,只有一个声明 - 它永远不会按照 p9.4.2.3 的要求“在命名空间范围中定义”。

\n\n

C++17

\n\n

那么是什么改变了呢?好吧,据我所知 - 没什么大不了的,但是在 C++17 中,通过采用P0389R2来定义静态数据成员的方式发生了一些变化,其目的 - 据我所知 - 引入了“内联变量”它可以简单地声明,然后在具有相同存储的多个翻译单元中使用,即只有一个使用初始值设定项初始化的变量实例初始化的变量实例- 这类似于其他语言中的“静态字段初始化”例如 Java,并且使单例渴望初始化变得更容易。

\n\n

这是规范中的内容:

\n\n
\n

声明是定义,除非 [...] 它在类定义中声明了非\xc2\xad 内联静态数据成员。

\n
\n\n

所以我们明确指定inline静态数据成员的声明一个定义。不需要类范围之外的外部定义。这无疑让程序员变得更容易。

\n\n

但这inline有什么关系constexpr呢?该提案的通过还导致了 p10.1.5.1 中的此规范:

\n\n
\n

使用说明符声明的函数或静态数据成员隐式constexpr是内联函数或变量。

\n
\n\n

因此,这一更改实际上非常明确地表明 的声明static constexpr bool noexcept_copy也是一个定义 - 在隐式模板实例化的情况下我们不能实例化它。

\n\n

我猜想这是一个足够强烈的信号,让 Clang 开发人员不要初始化static constexpr数据成员。

\n\n

顺便说一句,C++17 附录 D p.1 中的示例相当明确地解释了这一点:

\n\n
struct A {\n  static constexpr int n = 5; // definition (declaration in C++ 2014)\n};\nconstexpr int A::n; // redundant declaration (definition in C++ 2014)\n
Run Code Online (Sandbox Code Playgroud)\n\n

从这个示例中我们了解到,在 C++ 2014 中,a 的声明static constexpr不是定义,为了进行初始化,必须在命名空间范围内有一个定义。

\n\n

因此,Clang 在其 C++14 实现中输出错误是错误的,因为在 OP 代码中,不仅隐式实例化模板类静态数据成员是错误的 - 甚至没有它的定义,所以它不应该\'即使它不是模板类,也没有被实例化。

\n