函数指针是否在调用时使用

时间:2017-07-02 15:33:19

标签: c++ language-lawyer

这个问题是由评论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?

2 个答案:

答案 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-use

直观上讲,变量需要存储在内存中的某个位置。唯一的例外是,变量的值可以由编译器优化,并且从未以其他方式使用过。 Odr-usage使这一想法正式化:只有在使用odr时,变量才需要定义。

Odr-usage由[basic.def.odr]

定义
  

除非其将左值到右值转换应用于x,否则变量ex的名称将显示为可能评估的表达式exx将使用该变量不调用任何非平凡函数的常量表达式,如果x是对象,则ex是表达式e的潜在结果集中的一个元素,其中左值到右值转换适用于e,或者e是舍弃值表达式。

换句话说,x不会被使用

  1. x不会作为可能评估的表达式(例如decltype(x))出现。
  2. x不是对象(例如引用),并且将左值到右值转换应用于x会产生一个常量表达式。
  3. x是一个对象,将左值到右值转换应用于x会产生一个常数表达式。此外,还有一个“耦合”表达式e,该表达式已应用了左值到右值转换,或者被丢弃。

“耦合”是指直觉上有一些与x紧密相关的封闭表达式,只能以某些方式使用而无需将x存储在内存中。此概念通过潜在结果[basic.def.odr]

的定义而形式化
  

表达式e的潜在结果集定义如下:

     
      
  • 如果e是id表达式,则该集合仅包含e。

  •   
  • 如果e是具有数组操作数的下标操作,则该集合包含该操作数的可能结果。

  •   
  • 如果e是类成员访问表达式,则该集合包含对象表达式的可能结果。

  •   
  • 如果e是一个成员指向指针的表达式,其第二个操作数是一个常量表达式,则该集合包含对象表达式的可能结果。

  •   
  • 如果e的格式为(e1),则该集合包含e1的潜在结果。

  •   
  • 如果e是glvalue条件表达式,则该集合是第二和第三操作数的潜在结果集合的并集。

  •   
  • 如果e是逗号表达式,则该集合包含右操作数的可能结果。

  •   
  • 否则,该集合为空。

  •   

位于ex的潜在结果内的表达式ee“耦合”。

问题

应用定义

  1. m可能会被评估。
  2. 将左值到右值转换应用于m会产生一个常量表达式。
  3. m是一个对象。
  4. m是一个表达式,其潜在结果包括m,并且已应用从左值到右值的转换。

因此,我们得出结论:m没有被滥用。