这个问题是由评论here
引发的考虑以下代码
template <typename T, typename C>
void g(T, C) {}
template <typename T, typename C>
struct G
{
static constexpr void (*m) (T, C) = &g;
};
void foo()
{
auto l = [](int){return 42;};
G<int, decltype(l)>::m(420, l);
}
这在C ++ 17中是合法的,G::m
是通过内联变量在G
内定义的。
在C ++ 14和C ++ 11中,奇怪的是gcc拒绝这个陈述m
但是从未定义,而clang接受它。 Live
m
使用了吗?或者这是一个gcc bug?
答案 0 :(得分:-1)
这不是GCC错误。这是对c ++ 14的解释。
每个非内联函数或变量只有一个定义 必须使用奇数个(见下文) 程序(包括任何标准库和用户定义库)。的 不需要编译器来诊断这种违反情况,但是行为 违反程序的程序是不确定的。
错误是
source:7:29: error: 'constexpr void (* const G<int,
foo()::<lambda(int)> >::m)(int, foo()::<lambda(int)>)', declared using
local type 'foo()::<lambda(int)>', is used but never defined
[-fpermissive]
并且确实没有定义类型foo()::<lambda(int)>
,因为每个lambda都有唯一的类型。
用static constexpr void (*m) (T, C) = &g;
替换行static inline constexpr void (*m) (T, C) = &g;
使错误消失。
显然,这表明如果未标记为内联,则将使用m 。
我相信此消息是一种警告您的方式
auto l = [](int){return 42;};
G<int, decltype(l)>::m(420, l);
G<int, decltype(l)>::m(420, [](int){return 42;});
将导致以下错误(即使使用-std = c ++ 1z和GCC或CLANG)
source: In function 'void foo()': <source>:15:34: error: could not
convert 'g' from 'foo()::<lambda(int)>' to 'foo()::<lambda(int)>'
G<int, decltype(l)>::m(420, g);
因为lambda类型是唯一的。
但是,如果您使用-fpermissive
,则GCC会将错误转换为警告,这实际上是一种了解它不是GCC错误,而是过分保护以阻止某些做法的一种方式。
不使用-fpermissive消除歧义的一种方法是执行GCC建议并声明原型。
template <typename T, typename C>
void g(T, C) {}
template <typename T, typename C>
struct G
{
static constexpr void (*m) (T, C) = &g;
};
typedef int (*return_int)(int);
void foo()
{
auto l = [](int){return 42;};
G<int, return_int>::m(420, l);
G<int, return_int>::m(420, [](int){return 41;});
//Or even better if the aim is to use embedded template parameters
return_int ln = [](int){return 42;};
G<int, decltype(ln)>::m(420, ln);
}
此编译罚款。
最后,为什么GCC团队使用C ++ 17流行这种保护。我不知道他们可能会收到投诉,称其行为过于保护,而lambda类型的强制转换错误已足够。但是严重的错误
error: could not
convert 'g' from 'foo()::<lambda(int)>' to 'foo()::<lambda(int)>'
第一次有点不对劲……当遇到此错误时,您的头脑会怀疑,直到得出decltype([](int){return 42;})
与decltype([](int){return 42;})
不同的结论为止!当您尝试这个...
#include <random>
#include <iostream>
int main()
{
auto l = [](int) {return 42; };
std::cout << typeid(decltype(l)).name() << std::endl;
auto m = [](int) {return 42; };
std::cout << typeid(decltype(m)).name() << std::endl;
return 0;
}
输出(在Visual Studio中)
class <lambda_37799c61f9e31cc7b5f51a1bd0a09621>
class <lambda_818eb0a43a553fc43d3adadd7480d71e>
答案 1 :(得分:-1)
TLDR m
未被使用,这确实是GCC错误。
直观上讲,变量需要存储在内存中的某个位置。唯一的例外是,变量的值可以由编译器优化,并且从未以其他方式使用过。 Odr-usage使这一想法正式化:只有在使用odr时,变量才需要定义。
Odr-usage由[basic.def.odr]
定义除非其将左值到右值转换应用于
x
,否则变量ex
的名称将显示为可能评估的表达式ex
,x
将使用该变量不调用任何非平凡函数的常量表达式,如果x
是对象,则ex
是表达式e
的潜在结果集中的一个元素,其中左值到右值转换适用于e
,或者e
是舍弃值表达式。
换句话说,x
不会被使用
x
不会作为可能评估的表达式(例如decltype(x)
)出现。x
不是对象(例如引用),并且将左值到右值转换应用于x
会产生一个常量表达式。x
是一个对象,将左值到右值转换应用于x
会产生一个常数表达式。此外,还有一个“耦合”表达式e
,该表达式已应用了左值到右值转换,或者被丢弃。“耦合”是指直觉上有一些与x
紧密相关的封闭表达式,只能以某些方式使用而无需将x
存储在内存中。此概念通过潜在结果[basic.def.odr]
表达式
e
的潜在结果集定义如下:
如果
e
是id表达式,则该集合仅包含e。如果
e
是具有数组操作数的下标操作,则该集合包含该操作数的可能结果。如果
e
是类成员访问表达式,则该集合包含对象表达式的可能结果。如果
e
是一个成员指向指针的表达式,其第二个操作数是一个常量表达式,则该集合包含对象表达式的可能结果。如果
e
的格式为(e1)
,则该集合包含e1
的潜在结果。如果
e
是glvalue条件表达式,则该集合是第二和第三操作数的潜在结果集合的并集。如果
e
是逗号表达式,则该集合包含右操作数的可能结果。否则,该集合为空。
位于ex
的潜在结果内的表达式e
与e
“耦合”。
应用定义
m
可能会被评估。m
会产生一个常量表达式。m
是一个对象。m
是一个表达式,其潜在结果包括m
,并且已应用从左值到右值的转换。因此,我们得出结论:m
没有被滥用。