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(&)[N]
这是什么语法?我需要搜索什么来了解更多信息?
应该是const T(&list)[N]
@Ela782:在这种情况下,const T(&)[N]
是一个类型(对N
类型为const T
的元素的数组的左值引用)。它被用作未命名的函数参数。如果要为参数指定名称,请使用const T (&name)[N]
。
@Ela782 const &T[N]
声明了一个引用数组(在语义上是非法的),括号是声明对数组本身的引用所必需的。【参考方案2】:
编译器说问题是 init,而不是 init.size()。
我猜构造函数可以从不同的地方用不同长度的初始化器调用。
(详细说明:您正在尝试编写一个static_assert
,它取决于变量init
的运行时值,即它有多少元素。static_assert
s必须在编译函数时可以评估。您的代码类似于这个无效的示例:)
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_integralinit
不是时,'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<double> init)init.size()!=2?throw 0:other...code;
.
@alfC 看起来像yes - 但参数应该按值取值,const&
不起作用。 (在这种情况下你不需要throw
,static_assert
就足够了。throw
用于克服constexpr
的限制)。但看起来这仅适用于 constexpr
方法 do not depend on members
好吧,我想我正在尝试做一些不同的事情,这在原则上看起来是可行的,但我做不到。查看示例:coliru.stacked-crooked.com/a/9f4a561a5fd9178a 我想在编译时检查 constexpr initializer_list
的大小是否正确。
@alfC 我不确定,也许初始化列表真的应该返回 std::array<T, N>
而不是 std::initializer_list<T>
。
“initializer_list<T, N>
”在这里讨论过***.com/questions/7108425/…。是的,您可能是正确的,array<T, 2>
可能是我正在寻找的全部答案,因为它有效: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的主要内容,如果未能解决你的问题,请参考以下文章