在非整数类型的初始化期间强制执行编译时检查

时间:2018-02-20 13:50:00

标签: c++11 templates c++14 c++17 constexpr

我正在尝试确保使用有效数据初始化变量,并希望在编译时执行此检查。这些类有一个static constexpr方法来检查有效性。在简化版本中,它可能是这样的:

template <typename T>
struct Foo {
    constexpr Foo(T initValue) :
      mValue(initValue) 
    {
         assert(isValid(iniValue));
    }

    static constexpr bool isValid(T value) {
        return (value > 0);
    }        
private:
    T mValue;
};

现在对于整数类型,我可以使用模板化函数来强制执行编译时检查:

template <typename T, T initVal>
Foo<T> make_new() {
    static_assert(Foo<T>::isValid(initVal), "is not valid");
    return  Foo<T>(initVal);
}

并像这样使用

    auto saveInt = make_new<int, 3>();

问题是如何使用非整数类型来做到这一点。我可以做这样的事情,它会触发C ++ 14中的断言:

    constexpr Foo<float> constFloat(3.0);
    Foo<float> saveFloat = constFloat;

在C ++&gt; = 11中是否有一种更优雅的方式可以让我用单行程做到这一点?最后,我想要分配的值在编译时是已知的,因此该值的有效性也是如此。

2 个答案:

答案 0 :(得分:1)

使用当前标准no,您无法在编译时执行此操作。

Albuquerque ISO C++ Committee Meeting in 2017,对user-defined literals for stringsclass/struct types as non-type template parametersnew and delete in constexpr contexts的提案有正面反馈。所以在该标准的未来版本中(如果我们运气好,可能是C ++ 20),它将是可能的。在此之前,当前的限制只允许非类型模板参数的整数类型,并且没有我知道的解决方法。

至于当前限制的原因主要与表示,名称修改以及非整数类型的值的比较有关。

答案 1 :(得分:1)

如果constexpr检查并返回过滤器,其中包含非consexpr指令(异常的throw)错误的情况,如下所示

static constexpr T checkAndRet (T value)
 { return value > 0 ? value
                    : (throw std::runtime_error("invalid"), T{}); } 

您可以在分配前申请该值吗?

constexpr Foo(T initValue) : mValue{checkAndRet(initValue)} 
 { }

因此,当您声明constexpr

时,可能会出现编译时错误
constexpr Foo<float> f2 {-1.0f};  // compile time error

和运行时错误(异常)否则

Foo<float> f3 {-1.0f};  // run time error

以下是一个完整的工作示例,从C ++ 11开始工作

#include <stdexcept>

template <typename T>
struct Foo {
    constexpr Foo(T initValue) : mValue{checkAndRet(initValue)} 
     { }

    static constexpr T checkAndRet (T value)
     { return value > 0 ? value
                        : (throw std::runtime_error("invalid"), T{}); }

private:
    T mValue;
};

int main ()
 {
   Foo<float> f0 {1.0f};               // OK
   constexpr Foo<float> f1 {1.0f};     // OK
   //constexpr Foo<float> f2 {-1.0f};  // compile time error
   Foo<float> f3 {-1.0f};              // run time error
 }

- 编辑 -

OP指定以下

  

它无法解决我最初的问题。您的上一个代码行仍然只在运行时产生错误,即除非我事先将init值声明为Foo<float>,否则无法将constexpr变量初始化为在编译期间验证的值

抱歉:我部分误解了你的问题。

是;这是可能的,不事先声明constexpr Foo<float>,但我脑海中的解决方案非常难看,因为它很长并且需要重复元素。

使用C风格的宏,你可以使它简单而优雅,但我认为C风格的宏是邪恶的。

无论如何......如果您在static constexpr

中声明了以下Foo方法
static constexpr bool isValid (T value)
 { return value > 0 ? true
                    : (throw std::runtime_error("invalid"), false); }        

template <bool>
static constexpr T retV (T value)
 { return value; }

您可以使用constexpr字面值和编译时检查初始化非Foo<float> float对象,如下所示

// OK
Foo<float> f0 { Foo<float>::retV<Foo<float>::isValid(1.0f)>(1.0f) };

// compilation error
// Foo<float> f1 { Foo<float>::retV<Foo<float>::isValid(-1.0f)>(-1.0f) };

正如你所看到的那样非常难看,你必须重复两次这个值,这很容易出错。

但是如果你定义一个C风格的宏如下

#define makeCValue(val) \
   Foo<decltype(val)>::retV<Foo<decltype(val)>::isValid(val)>(val)

您可以按如下方式初始化Foo<float>()个对象

Foo<float> f2 { makeCValue(1.0f) };     // OK
// Foo<float> f3 { makeCValue(-1.0f) }; // compilation error

以下是一个完整的工作示例

#include <stdexcept>

template <typename T>
struct Foo
 {
   constexpr Foo(T initValue) : mValue{initValue} 
    { }

   static constexpr bool isValid (T value)
    { return value > 0 ? true
                       : (throw std::runtime_error("invalid"), false); }

   template <bool>
   static constexpr T retV (T value)
     { return value; }

   T mValue;
 };

#define makeCValue(val) \
   Foo<decltype(val)>::retV<Foo<decltype(val)>::isValid(val)>(val)


int main ()
 {
   // OK
   Foo<float> f0 { Foo<float>::retV<Foo<float>::isValid(1.0f)>(1.0f) };

   // compilation error
   // Foo<float> f1 { Foo<float>::retV<Foo<float>::isValid(-1.0f)>(-1.0f) };

   Foo<float> f2 { makeCValue(1.0f) };     // OK
   // Foo<float> f3 { makeCValue(-1.0f) }; // compilation error
 }

显然,您只能将makeCValue()宏用于编译时已知值(文字值,constexpr值,...)。