一种安全的,符合标准的方法,只有在实例化了static_assert的情况下,才能使类模板专业化吗?

时间:2019-06-16 12:49:53

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

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

#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来回答该问题。我认为我的问题有所不同,因为我只是在问我的示例,我不确定标准对此的看法。

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

  • [温度深度1]:

      

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

    这应该使std::is_same<C,T>::value依赖于T

  • [temp.res.8.1]:

      

    如果模板中的语句或模板未实例化,则无法为模板或constexpr的子语句生成有效的专业化,或者

    因为存在有效的专业化,所以不适用,特别是OnlyNumbers<C>是有效的,可以在类内部使用,例如定义成员指针变量(ptr)。实际上,通过删除断言并取消注释ptrOnlyNumbers<Foo>行,即可编译代码。

  • [温度解析度8.2-8.4]不适用。

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

我的问题是:我的推理正确吗?仅当实例化模板时,才能使用static_assert编译特定[class] *模板的安全,标准方法吗?

*主要是我对类模板感兴趣,可以随时包含函数模板。但是我认为规则是相同的。

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

编辑:

请澄清一下,我知道我可以构造一个表达式,如果其他任何专业都不匹配,则该表达式将完全为假。但这很快就会变得冗长,并且如果专业领域发生变化,很容易出错。

3 个答案:

答案 0 :(得分:2)

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

#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实例化。由于静态断言无论如何都会失败,因此不会产生负面影响。

答案 1 :(得分:1)

嗯...我不明白你对

的含义
  

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

您能举例说明OnlyNumers有效并基于OnlyNumbers<C>编译主模板吗?

无论如何,在我看来,重点就在于此。

如果你问

  

仅在实例化**的情况下,使用static_assert编译特定的[class] *模板是否安全,符合标准?

在我看来(可能不包括仅在另一个专业匹配的情况下才为真的测试)由于[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>>>
 { };

答案 2 :(得分:1)

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

{
data:
  "bank",
  render: function(data, type, row){
          var selectBank =
           '<select class="custom-select form-control renderBank" id="" name="billNotes['+index+'].billingNote.bank.id" data-url="/api/finance/master/banks" data-selected="'+data.id+'">';

   renderSelect2Bank(data.id);
   return selectBank;
  }
},

function renderSelect2Bank(selectedBank){
$('select.renderBank').each(function (index) {

    var that = this;
    $(that).select2({
        width: '100%',
        placeholder: "Silah pilih",
        language: "id"
    });

    $.ajax({
        url: $(that).data('url'),
        type: 'GET',
        success: function(data){
            if(data && data !== ""){
                $.each(data, function (i, val){
                    if(selectedBank === val.id){
                        $(that).append(initAutofill(val.text, val.id, true));
                    }else{
                        $(that).append(initAutofill(val.text, val.id, false));
                    }
                });
            }
        },
        error: function(xhr, status, error){
            errorCallback(xhr);
        }
    });
});

两句话:

  • 首先,我故意替换了错误消息,因为错误消息“不是算术类型”的尖叫声表明您必须测试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<T>::value以击败静态断言。我要说的就是他们的问题。请记住,每当您做出某种白痴证明时,自然就会使白痴变得更好。 ;-)认真地说, do 使API易于使用且不易滥用,但是您无法修复精神错乱,也不必费心尝试。

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