【问题标题】:A safe, standard-compliant way to make a class template specialization fail to compile using `static_assert` only if it is instantiated?一种安全的、符合标准的方法来使类模板特化仅在实例化时才使用“static_assert”编译失败?
【发布时间】:2019-06-16 12:49:53
【问题描述】:

假设我们要创建一个只能用数字实例化的模板类,否则不应该编译。我的尝试:

#include <type_traits>

template<typename T, typename = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(std::is_same<C,T>::value, "T is not arithmetic type.");

    //OnlyNumbers<C>* ptr;
};

template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>{};

struct Foo{};
int main()
{
    OnlyNumbers<int>{}; //Compiles
    //OnlyNumbers<Foo>{}; //Error
}

Live demo - 所有三个主要编译器似乎都按预期工作。我知道已经有一个类似的question 的答案引用了标准。接受的答案使用temp.res.8temp.dep.1 来回答该问题。我认为我的问题有点不同,因为我准确地询问了我的示例,并且我不确定标准对此的看法。

我认为我的程序不是格式错误的,并且当且仅当编译器尝试实例化基本模板时它才应该编译失败。 我的推理:

  • [temp.dep.1]:

    在模板内部,一些结构的语义可能因实例而异。这样的构造取决于模板参数。

    这应该使std::is_same&lt;C,T&gt;::value 依赖于T

  • [temp.res.8.1]:

    不能为模板或 constexpr 的子语句生成有效的特化,如果模板内的语句和模板未实例化,或

    不适用,因为存在有效的特化,特别是 OnlyNumbers&lt;C&gt; 是有效的,可以在类中使用,例如定义一个成员指针变量(ptr)。实际上,通过删除断言并取消注释 ptrOnlyNumbers&lt;Foo&gt; 行代码编译。

  • [temp.res.8.2 - 8.4] 不适用。

  • [temp.res.8.5] 我认为这也不适用,但我不能说我完全理解这一部分。

我的问题是:我的推理正确吗?这是使特定 [class]* 模板无法使用 static_assert if** 且仅在实例化时无法编译的安全且符合标准的方法吗?

*我主要对类模板感兴趣,请随意包含函数模板。但我认为规则是一样的。

**这意味着没有T 可以用来从外部实例化模板,就像T=C 可以从内部使用一样。即使C 可以以某种方式访问​​,我认为没有办法引用它,因为它会导致这种递归OnlyNumbers&lt;OnlyNumbers&lt;...&gt;::C&gt;

编辑:

澄清一下,我知道如果没有其他专业匹配,我可以构造一个完全错误的表达式。但这很快就会变得冗长,而且如果专业发生变化,很容易出错。

【问题讨论】:

    标签: c++ templates language-lawyer static-assert


    【解决方案1】:

    静态断言可以直接在类中使用,而不需要做任何复杂的事情。

    #include <type_traits>
    
    template<typename T>
    struct OnlyNumbers {
        static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
        // ....
    };
    

    在某些情况下,您可能会收到其他错误消息,因为为非算术类型实例化 OnlyNumbers 可能会导致更多编译错误。

    我不时使用的一个技巧是

    #include <type_traits>
    
    template<typename T>
    struct OnlyNumbers {
        static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
        using TT = std::conditional_t<std::is_arithmetic_v<T>,T,int>;
        // ....
    };
    

    在这种情况下,您的类将使用有效类型 int 进行实例化。由于静态断言无论如何都会失败,因此这不会产生负面影响。

    【讨论】:

      【解决方案2】:

      嗯...我不明白你的意思是什么

      [[temp.res.8.1]] 不适用,因为存在有效的特化,特别是 OnlyNumbers 是有效的并且可以在类中使用,例如定义一个成员指针变量(ptr)。

      你能举一个OnlyNumers有效并基于OnlyNumbers&lt;C&gt;编译主模板的例子吗?

      无论如何,在我看来,重点正是如此。

      如果你问

      这是一种安全、符合标准的方法,可以使特定 [class]* 模板无法使用 static_assert if** 进行编译,并且仅当它被实例化时?

      在我看来(也许排除只有在另一个专业匹配时才为真的测试)答案是“否”,因为 [temp.res.8.1]。

      也许您可以打开一扇小门以允许实例化,但只有真正(真的!)想要实例化它的人才能使用。

      例如,您可以添加第三个模板参数,具有不同的默认值,如下所示

      template<typename T, typename U = void, typename V = int>
      struct OnlyNumbers
       {
         static_assert(std::is_same<T, U>::value, "test 1");
         static_assert(std::is_same<T, V>::value, "test 2");
       };
      

      这样你就打开了一扇通往合法实例化的大门

      OnlyNumbers<Foo, Foo, Foo>     o1;
      OnlyNumbers<void, void, void>  o2;
      OnlyNumbers<int, int>          o3;
      

      但仅说明至少第二种模板类型。

      不管怎样,你为什么不干脆避免定义模板的主版本?

      // declared but (main version) not defined
      template<typename T, typename = void>
      struct OnlyNumbers;
      
      // only specialization defined
      template<typename T>
      struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>
       { };
      

      【讨论】:

      • 感谢您的回答。显然我错了,我认为类中的OnlyNumbers&lt;C&gt; 会创建一个有效的模板,但事实并非如此。因为它将创建另一个C,它不同于作为参数传递的那个。留下未声明的主模板有效,但我只是不喜欢错误消息。使用自定义的 static_assert 更具可读性。添加额外的参数确实是我最初的想法,但它应该是无效的T,我的解决方案是尝试当场使这个无效T,因此无法访问C。所以我会坚持只声明。
      【解决方案3】:

      您的代码格式错误,因为无法实例化主模板。请参阅 Barry 对您链接到的相关问题的回答中的标准引用。您用来确保无法满足明确规定的标准要求的迂回方式无济于事。停止与您的编译器 rsp 作斗争。标准,并采用Handy999的方法。如果您仍然不想这样做,例如出于 DRY 的原因,实现目标的一致方法是:

      template<typename T, typename Dummy = void>
      struct OnlyNumbers{
      public:
          struct C{};
          static_assert(! std::is_same<Dummy, void>::value, "T is not a number type.");
      

      两个备注:

      • 首先,我故意替换了错误信息,因为错误信息“不是算术类型”尖叫,你必须测试! std::is_arithmetic&lt;T&gt;::value。如果您对“数字”类型有多个重载,其中一些满足标准的算术类型要求而其他可能不满足(例如,可能来自多精度库的类型),我概述的方法可能是有意义的。
      • 其次,您可能会反对有人可以写,例如OnlyNumbers&lt;std::string, int&gt; 打败静态断言。我要说的是,这是他们的问题。请记住,每次您证明白痴时,大自然都会成为更好的白痴。 ;-) 说真的,确实制作了易于使用且难以滥用的 API,但您无法解决精神错乱,因此不应该费心尝试。

      TL;DR:KISS 和 SWYM(说出你的意思)

      【讨论】:

        猜你喜欢
        • 2018-06-10
        • 1970-01-01
        • 1970-01-01
        • 2014-10-21
        • 1970-01-01
        • 2019-09-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多