initializer_list::size() 上的 static_assert

Posted

技术标签:

【中文标题】initializer_list::size() 上的 static_assert【英文标题】:static_assert on initializer_list::size() 【发布时间】:2011-07-23 06:13:50 【问题描述】:

为什么 std::initializer_list<_E>::size 不允许在 static_assert 中使用,即使它在我的 libstdc++ (v. 4.6) 中声明为 constexpr

例如下面的代码:

template<class T, int Length>
class Point

  public:
    Point(std::initializer_list<T> init)
    
      static_assert(init.size() == Length, "Wrong number of dimensions");
    
;

int main()

  Point<int, 3> q(1,2,3);

  return 0;

给出以下错误:

test.C: In constructor ‘Point<T, Length>::Point(std::initializer_list<_Tp>) [with T = int, int Length = 3]’:
test.C:60:26:   instantiated from here
test.C:54:7: error: non-constant condition for static assertion
test.C:54:73:   in constexpr expansion of ‘init.std::initializer_list<_E>::size [with _E = int, std::initializer_list<_E>::size_type = long unsigned int]()’
test.C:54:7: error: ‘init’ is not a constant expression

请注意,这对于一个简单的示例来说效果很好:

class A

  public:
    constexpr int size()  return 5; 
;

int main()

  A a;
  static_assert(a.size() == 4, "oh no!");

  return 0;

【问题讨论】:

看起来它应该按照你想要的方式工作。 是的,我想知道这是否是编译器错误?如果我在这里犯了错误,我不想打扰 gcc 人员,但是查看 initializer_list 头文件让我相信这里有问题。 我了解 size() 在 libstdc++ 中被声明为 constexpr,但需要注意的是标准并不要求这样做。所以即使你让这个工作(例如,也许使用 Evgeny Panasyuk 的方法如下),你不能依赖它来与标准库的其他实现一起工作。 再一次,C++14 似乎正在发生变化,请参阅 18.9/1。构造函数size()begin()end()在C++14提案中都声明为constexpr 这似乎仍然不适用于 Clang 3.5 和 C++14。这令人困惑。 【参考方案1】:

“初始化列表”只是可怕的杂物。

不要:

#include <initializer_list>

template<typename T>
void Dont(std::initializer_list<T> list)  // Bad!
    static_assert(list.size() == 3, "Exactly three elements are required.");


void Test()  Dont(1,2,3); 

做:

template<typename T, std::size_t N>
void Do(const T(&list)[N])  // Good!
    static_assert(N == 3, "Exactly three elements are required.");


void Test()  Do(1,2,3); 

【讨论】:

@Quant:这些是 Clang 错误。从 3.8 开始工作。 const T(&amp;)[N] 这是什么语法?我需要搜索什么来了解更多信息? 应该是const T(&amp;list)[N] @Ela782:在这种情况下,const T(&amp;)[N] 是一个类型(对N 类型为const T 的元素的数组的左值引用)。它被用作未命名的函数参数。如果要为参数指定名称,请使用const T (&amp;name)[N] @Ela782 const &amp;T[N] 声明了一个引用数组(在语义上是非法的),括号是声明对数组本身的引用所必需的。【参考方案2】:

编译器说问题是 init,而不是 init.size()。

我猜构造函数可以从不同的地方用不同长度的初始化器调用。

(详细说明:您正在尝试编写一个static_assert,它取决于变量init 的运行时,即它有多少元素。static_asserts必须在编译函数时可以评估。您的代码类似于这个无效的示例:)

void foo(int i)  static_assert(i == 42, ""); 
int main()  foo(42);   // but what if there's a caller in another translation unit?

【讨论】:

是的,但这就是重点——每当有人使用大小不合适的初始值设定项列表调用构造函数时,我想抛出一个 static_assertion。因为initializer_list是由编译器构造的(没有公共构造函数),又因为size()方法是constexpr,所以我的static_assert应该是完全可以的。 为什么不呢?想象一下,我有一个接受 T2 类型参数的模板化构造函数。放置一个 static_assert 以确保(例如)std::is_integral::value 解析为“真”是完全可以接受的。这是有效的,因为 std::is_integral::value 是一个编译时间常数,任何标记为 constexpr 的东西也是如此。 std::initializer_list::size() 可以标记为 constexpr 的原因是因为它有一个特殊的私有构造函数,只有编译器可以访问。 是的,但是当init 不是时,'size()` 是 constexpr 并没有帮助。编译器抱怨init 是的,但是为什么我的简单示例有效?对象“a”不是常量,但有一个 constexpr size() 方法,在 static_assert 中工作得很好。 @Boatzart:问题是,编译器为每个T2 生成不同的构造函数,在编译时所有不同的可能性都已经存在。但是您不知道在编译时是否传递了大小为 5 或大小为 3 的 initializer_list。并且静态断言是在编译时,所以编译器需要知道所有不同的可能性。【参考方案3】:

从我与@Evgeny 的讨论中,我意识到这很有效(使用gcc 4.8 c++11)并且还可以通过只接受初始化列表中的兼容大小(在main)中进行大小检查。

(代码链接:http://coliru.stacked-crooked.com/a/746e0ae99c518cd6)

#include<array>
template<class T, int Length>
class Point

  public:
    Point(std::array<T, Length> init)
    
//not needed//      static_assert(init.size() == Length, "Wrong number of dimensions");
    
;

int main()

  Point<int, 3> q(1,2,3); //ok
//  Point<int, 3> q2(1,2,3,4); //compile error (good!)
  Point<int, 3> q2(1,2); // ok, compiles, same as 1,2,0, feature or bug?
  return 0;

【讨论】:

相关:***.com/questions/32999822/…【参考方案4】:

使用以下语法:

LIVE DEMO

#include <initializer_list>

template<class T, int Length>
class Point

    std::initializer_list<T> data;
public:
    constexpr Point(std::initializer_list<T> init)
        : data
        (
            init.size() == Length ?
            init : throw 0
        )
    
;

int main()

    constexpr Point<int, 3> a1,2,3;
    constexpr Point<int, 2> b1,2,3; // compile time error

请参考following SO。


编辑:有趣的是适用于 GCC 4.8.1,但不适用于 Clang 3.4。也许这与.size()constexpr 有关(afaik,在C++14 中是constexpr)。

【讨论】:

有趣,这个条件可以应用于非constexpr 函数,而是应用于constexpr 参数(如果那个东西存在的话)。例如void fun(double param, [constexpr] std::initializer_list&lt;double&gt; init)init.size()!=2?throw 0:other...code;. @alfC 看起来像yes - 但参数应该按值取值,const&amp; 不起作用。 (在这种情况下你不需要throwstatic_assert 就足够了。throw 用于克服constexpr 的限制)。但看起来这仅适用于 constexpr 方法 do not depend on members 好吧,我想我正在尝试做一些不同的事情,这在原则上看起来是可行的,但我做不到。查看示例:coliru.stacked-crooked.com/a/9f4a561a5fd9178a 我想在编译时检查 constexpr initializer_list 的大小是否正确。 @alfC 我不确定,也许初始化列表真的应该返回 std::array&lt;T, N&gt; 而不是 std::initializer_list&lt;T&gt; initializer_list&lt;T, N&gt;”在这里讨论过***.com/questions/7108425/…。是的,您可能是正确的,array&lt;T, 2&gt; 可能是我正在寻找的全部答案,因为它有效:coliru.stacked-crooked.com/a/8fe9af280f3baffc。我想我在 C++98(和boost::array)中放弃了这个,因为它在传递中不起作用,但现在它起作用了!!。 (现在剩下的就是在 C++2x 中等待initializer_tuple。)【参考方案5】:

我还没有真正弄清楚这里发生了什么。

如果我说

const std::initializer_list<double> li =  1, 2.5, 3.7, 4.3 ;
static_assert(li.size() == 4, "fail");

我收到一条抱怨说“li”没有被声明为“constexper”。

如果我说

constexpr std::size_t dSize(std::initializer_list<double> di)

    return di.size();

然后

static_assert(dSize(1, 2.5, 3.7, 4.3) == 4, "failed");   // works

但是

static_assert(dSize(li) == 4, "failed");

失败并显示“错误:'li' 的值在常量表达式中不可用”

这都是 -std=c++11

因此,不知何故,传递给 arg 列表的初始化器列表可以是常量表达式的一部分,但刚刚声明为 const 变量的初始化器列表不能。

【讨论】:

constexpr (function dSize) 并不意味着它会返回一个 constexpr。如果满足某些条件,它将“尝试”这样做(似乎永远不会使用std::initializer_list),这就是最后一个static_assert 不起作用的原因。 constexpr 应用于变量和函数时的含义不同。 我知道我做错了什么。当我声明 'constexpr' 初始化列表时,我是在 'main' 中执行的,所以它是一个自动变量。当我将声明移到任何函数之外,使其成为全局变量或静态变量时,“constexpr”正常工作,静态断言也正常工作。 其实很有趣。虽然全局 initializer_list 不会很有用。

以上是关于initializer_list::size() 上的 static_assert的主要内容,如果未能解决你的问题,请参考以下文章