11

我想强制模板实例化。
以下代码1在 g++ ( http://coliru.stacked-crooked.com/a/33986d0e0d320ad4 ) 上工作 (打印)。
但是,它会0在 Visual C++ ( https://rextester.com/WGQG68063 ) 上打印错误的结果 ( )。

#include <iostream>
#include <string>
template <int& T>struct NonTypeParameter { };

//internal implementation
int lala=0;
template <typename T> struct Holder{
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

//tool for user 
template <typename T> struct InitCRTP{ 
    using dummy=NonTypeParameter<Holder<T>::init>;
};

class WantInit : public InitCRTP<WantInit>{};//user register easily
int main(){
    std::cout << lala << std::endl;
}

它是 Visual C++ 编译器错误,还是一种未定义的行为?
如果是 Visual C++ 错误,如何解决它(同时仍然保持美观)?

编辑:按照 Max Langhof(和许多人)的建议更改类 -> 结构。感谢。

赏金原因

StoryTellerMaxim Egorushkin的相反解决方案以及他们的深入讨论(谢谢!),这听起来像是 C++ 规则的模糊区域。

如果是 Visual C++ 错误,我希望问题能够确定地报告。

此外,我仍然希望有一个很好的解决方法,因为这种技术对于自定义类型 ID 生成非常有用。显式实例化不是那么方便。

注意:我将赏金授予Kaenbyo Rin,因为对我来说,这很容易理解。
这并不意味着其余的答案不太正确或不太有用。
我仍然不确定哪个是正确的。读者应谨慎行事。
为了安全起见,我假设我只是不能使用该功能(目前)。感谢大家。

4

4 回答 4

6

当然,其中涉及编译器错误。我们可以通过改变InitCRTP一点来验证它:

template <typename T, typename = NonTypeParameter<Holder<T>::init>>
struct InitCRTP {
};

现在提到任何InitCRTP<T>特化都必须使用Holder<T>::init来确定第二个模板参数。这反过来应该强制实例化Holder<T>::init,但VS 不会实例化

一般来说,使用 CRTP 类作为基础应该已经实例化了类内的所有声明,包括dummy. 所以这也应该奏效。

我们可以进一步验证。当用作基时,成员函数的声明与类一起实例化:

template <typename T> struct InitCRTP{
    using dummy=NonTypeParameter<Holder<T>::init>;
    void dummy2(dummy);
};

尽管如此,VC++ 还是很顽固。鉴于所有这些,以及 Clang 和 GCC 所表现出的行为,这是一个 VC++ 错误。

于 2019-09-16T10:06:38.683 回答
4

class WantInit : public InitCRTP<WantInit>既没有实例化InitCRTP<WantInit>::dummy,也没有Holder<WantInit>::init因为程序中实际使用的东西没有引用它们。代码中的隐式实例化链不需要实例化Holder<T>::init,请参阅隐式实例化

这适用于类模板的成员:除非该成员在程序中使用,否则它不会被实例化,并且不需要定义。

解决方法是使用显式模板实例化

template struct Holder<void>;

这会导致Holder<void>与它的所有非模板成员一起被实例化。

或者,您可以仅实例化Holder<T>::init成员,例如:

static_cast<void>(Holder<void>::init);

IMO、gcc 和 clang 过于渴望实例化没有被引用的东西。这种行为不会破坏或拒绝有效代码,因此这几乎不是错误,但依赖于这种特定行为的副作用是脆弱且不可移植的。

于 2019-09-16T09:35:52.893 回答
1

让我们尝试绝对 ODR 使用该init成员。

#include <iostream>
#include <string>

int lala=0;
template <typename T> struct Holder{
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

template <typename T> struct InitCRTP{
    InitCRTP() { (void)Holder<T>::init; }
};

class WantInit : public InitCRTP<WantInit>{};
int main(){
    std::cout << lala << std::endl;
    // WantInit w;  <---------------------------- look here
}

现在,如果注释掉的行未注释,程序的结果会发生变化。恕我直言,任何东西的模板实例化状态或 ODR 使用状态不可能取决于是否调用了某些非模板函数(在这种情况下为 WantInit 构造函数)。我会说有一种相当强烈的臭虫气味。

于 2019-09-26T10:41:39.553 回答
1

dummy我相信@MaximEgorushkin 关于没有真正实例化的事实是正确的。

dummy被声明(因为它是一个类型别名声明),并且为了声明这个别名,NonTypeParameter<Holder<T>::init>被声明。为了声明NonTypeParameter<Holder<T>::init>,它的模板参数Holder<T>::init必须被声明,因此Holder<T>也被声明。

该标准要求在实例化模板类时,定义其删除的成员函数。[温度规格]

类模板特化的隐式实例化导致:[...]

—— 已删除成员函数、无作用域成员枚举、成员匿名联合定义的隐式实例化。

并且引用void应该会导致编译错误。

我们可以用它来测试一个特定的模板是否是专门的。

#include <iostream>
#include <string>
template <int& T, typename U> struct NonTypeParameter { 
    U& f() = delete;
};

//internal implementation
int lala = 0;
template <typename T> struct Holder {
    T& f() = delete;
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

//tool for user 
template <typename T> struct InitCRTP {
    using dummy = NonTypeParameter<Holder<T>::init, void>;
};

class WantInit : public InitCRTP<WantInit> {};//user register easily
int main() {
    std::cout << lala << std::endl;
}

此代码将编译,因为NonTypeParameter<Holder<T>::init, void>仅声明,未实例化。

但是如果我们class WantInit : public InitCRTP<WantInit>变成

class WantInit : public InitCRTP<void>

在 MSVC、g++ 和 clang 中编译失败。

这是因为 的声明NonTypeParameter<Holder<void>::init, void>需要 的隐式实例化Holder<void>

OP 遇到的问题完全是由于 MSVC 对Holder<T>::init使用 ODR 的无知:

#include <iostream>

template <int& T> struct NonTypeParameter { };

int lala = 0;

template <typename T> struct Holder {
    static int init;
};

template <typename T> int Holder<T>::init = lala++;

int main() {
    NonTypeParameter<Holder<int>::init> odr;
    std::cout << lala << std::endl;
}

MSVC 将输出0. 这意味着它没有意识到它Holder<int>::init已被 ODR 使用。

编译器资源管理器链接

于 2019-10-02T04:25:01.913 回答