在下面的C++ 11代码中,对arraySize的最后一次调用会导致编译错误.显然这是因为y是运行时大小的数组,并且不能推导出y的arraySize模板参数N. 我不明白为什么x是一个编译时大小的数组,但y结束了运行时大小.arraySize模板函数直接取自Scott Meyers的"Effective Modern C++"第1项.
#include <cstddef>
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N]) noexcept { return N; }
struct S
{
char c[10];
};
int main()
{
S s;
S* ps = &s;
char x[arraySize(s.c)];
char y[arraySize(ps->c)]; // why is y a runtime sized array?
arraySize(x);
arraySize(y); // error !?
return 0;
}
Run Code Online (Sandbox Code Playgroud) N2976建议添加constexpr标准库中的某些位置.它指出iostreams不适用于constexprEXCEPT结束迭代器.所以istream_iterator,istreambuf_iterator并给予constexpr默认构造函数,就是这样.例如,您可以在libstdc ++实现中看到constexpr只在整个文件中出现一次.引发这一变化的LWG是#1129.它说:
istream_iterator并istreambuf_iterator应支持文字哨兵价值观.默认构造函数经常用于终止范围istreambuf_iterator,并且istream_iterator在迭代值类型时很容易成为文字值 .[其余省略]
这对我来说并没有多大意义.有人能告诉我他们的意思吗?
N3308是另一篇提到但没有解释问题的论文:
某些
istream_iterator<T>构造函数必须是constexprifT是文字类型.目的是允许存储一种T内联类型的现有实现技术继续工作.[libstdc ++这样做,_Tp _M_value]但是,它实际上排除了这种技术:T不需要标记默认和复制构造函数constexpr,如果不是,则istream_iterator<T>构造函数不能被实例化constexpr.
上面解释了普通的复制构造函数和析构函数,但不是默认构造函数标记为constexpr的原因.
此外,在在线GCC 5.2.0上进行测试,我复制了libstdc ++的实现.唯一的变化是我删除了constexpr istream_iterator().在这两种情况下,组件都是相同的.
C++ 17更新:
static constexpr变量是隐式的,inline因此不需要外部定义.
原始问题:
假设我有一个常量列表,例如
struct Cls {
static constexpr int N = 32;
static constexpr int M = 64;
};
Run Code Online (Sandbox Code Playgroud)
这当然表明我为这些添加定义以避免可能发生的ODR使用问题,因此我需要:
constexpr int Cls::N;
constexpr int Cls::M;
Run Code Online (Sandbox Code Playgroud)
为什么要我喜欢这个了
struct Cls {
enum : int {
N = 32,
M = 64
};
};
Run Code Online (Sandbox Code Playgroud)
从而节省了我的ODR使用难题N,M并且更真实地只是常量而不是它们自己的对象(如果这只是标题,那就更大了)并且更短.enum : long long如果需要,我可以明确指定类型或其他任何东西.第一个有什么优势?
在放宽constexpr的规则之后,似乎这些功能可以在任何地方使用.它们也可以在常量(constexpr)和局部(可变)变量上调用.所以对我来说,它似乎只是编译器的一个提示(如内联).我只是继续在任何地方写它并在编译器抱怨时删除它.因此,如果可以在编译时评估函数,编译器似乎知道所有内容.为什么它不是默认行为,为什么我必须将任何东西标记为constexpr?
根据n4487和其他c ++ 17引用,将会有新的lambda函数说明符 - constexpr如果存在,则"明确指定函数调用运算符是一个constexpr函数." .我理解lambda中常量表达式的动机.对我来说有趣的是提案的第4点,其中指出:
4)如果
constexpr在lambda声明符中省略了说明符,则函数调用运算符(或模板)constexpr是否满足constexpr函数的要求.
这引出了两个问题:
constexpr说明符?看起来lambda调用操作符是否将constexpr取决于它"满足constexpr函数的要求"的事实,而不是来自 constexpr说明符的存在.constexpr默认情况下可以接受lambda,那么为什么不建议其他类型的函数 - 例如全局函数?如果编译器开始处理涵盖需求的所有函数,会产生什么影响constexpr?我习惯用定义我的常量enum { my_const = 123; },因为在类中,使用static constexpr需要在类定义之外的一些代码(参见这个问题).但是 - 功能体呢?最近我注意到人们只是constexpr在他们的功能中有变量(const实际上甚至没有打扰他们),我想知道我是不是一个傻瓜谁是我的时代背后
int foo(int x)
{
enum : int { bar = 456 };
return x + bar;
}
Run Code Online (Sandbox Code Playgroud)
所以,我的问题是:在函数体中使用枚举而不是constexpr变量有什么好处吗?
C++17§10.1.5/ 1指出:
的
constexpr说明符将只应用于一个变量或变量模板的定义或功能或功能模板的声明.使用说明constexpr符声明的函数或静态数据成员 隐式地是内联函数或变量(10.1.6).如果函数或函数模板的任何声明都有一个constexpr说明符,那么它的所有声明都应该包含说明constexpr符.
因为类似的段落已在标准中存在C++ 11(§7.1.5/ 1),这是在引述由理查德·史密斯评论,其中他争辩说,C++标准并没有要求constexpr说明符之间匹配声明和变量的定义.在上段的最后一句明确要求constexpr符横跨匹配功能和函数模板的声明,但并没有提及变量声明.
§10.1.5/ 9指出:
constexpr对象声明中使用的说明符将对象声明为const.这样的对象应具有文字类型并应初始化.在任何constexpr变量声明中,初始化的完整表达式应为常量表达式(8.20).
当然,如果我们有一个单独的声明和定义,它们都需要匹配const,无论constexpr指定符是否需要匹配.
§12.2.3.2/ 2-3说:
2在其类定义中声明非内联静态数据成员不是定义,并且可能是除cv 之外的不完整类型
void.未在类定义中内联定义的静态数据成员的定义应出现在包含成员类定义的命名空间范围内.在命名空间作用域的定义中,静态数据成员的名称应使用::运算符通过其类名限定.静态数据成员定义中的初始化表达式在其类的范围内(6.3.7).3如果非易失性非内联
const静态数据成员具有整数或枚举类型...如果使用说明constexpr符声明成员,则可以在没有初始化程序的命名空间范围内重新声明该成员 (此用法已弃用;请参阅D.1 ).其他静态数据成员的声明不应指定大括号或等于初始化器.
§D.1/ 1内容如下:
为了与先前的C++国际标准兼容,
constexpr可以在类外部冗余地重新声明静态数据成员而不使用初始化程序.不推荐使用此用法.
从中我们可以收集到,如果使用说明符声明成员constexpr,则命名空间范围定义是多余的, …
实际上这个"问题"感觉非常简单.在做一些计算出的图标偏移时,我想出了以下方法:
namespace Icons {
struct IconSet {
constexpr IconSet(size_t base_offset) noexcept
: base_offset_(base_offset), icon(base_offset * 3), iconSmall(icon + 1), iconBig(icon + 2) {
}
size_t icon;
size_t iconSmall;
size_t iconBig;
size_t base_offset_;
constexpr size_t next() const {
return base_offset_ + 1;
}
};
static constexpr IconSet flower = IconSet(0);
static constexpr IconSet tree = IconSet(flower.next());
static constexpr IconSet forest = IconSet(tree.next());
static constexpr IconSet mountain = IconSet(forest.next());
}
Run Code Online (Sandbox Code Playgroud)
现在可以编写一个Icons::tree.iconBig例子来获取该图标的计算偏移量.基本上,设计师可以更改图标 - 有时也可以添加/删除 - 但总是按惯例提供整个设置(正常,小和大).
如你所见,这种方法的问题是我必须做这个next()功能并重复使用它 - 正常的枚举不会有这个缺点. …
考虑一个简单的例子:
int foo() {
return 3;
}
template <int>
struct Bar {};
int a;
int main() {
int b;
//Bar<((void)foo(), 1)> bar1; //case 1. compilation error as expected
Bar<((void)a, 2)> bar2; //case 2. no error (long shot but `a' has a linkage so maybe expected)
Bar<((void)b, 3)> bar3; //case 3. no error ? (`b' does not have linkage)
(void)bar2;
(void)bar3;
}
Run Code Online (Sandbox Code Playgroud)
理想情况下,我想写:
constexpr std:map<std::string, std::string> my_map =
{{"key1", "val1"}, {"key2", "val2"}, };
Run Code Online (Sandbox Code Playgroud)
要么
constexpr std:unordered_map<std::string, std::string> my_map =
{{"key1", "val1"}, {"key2", "val2"}, };
Run Code Online (Sandbox Code Playgroud)
但是都不可能(至少在当前语言c ++ 17中是不可能的)
那还有什么选择呢?
例如,我可以做以下事情:
constexpr std::initializer_list< std::pair<const char*, const char *> > my_map =
{{"key1", "val1"}, {"key2", "val2"}, };
Run Code Online (Sandbox Code Playgroud)
然后在运行std::map时需要时构造一个。我知道我也可以使用inline代替constexpr,但是那时我将无法在编译时使用(也许在开发的后期)。但是,这有点不令人满意(因为我不得不在运行时仅出于查找目的构造映射),我想知道:是否有更好的解决方案?