Is a function pointer odr-used if it is called

Viewed 514

This question is sparked by a comment here

Consider the following code

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);
}

This is legal in C++17 everywhere, G::m is defined within G via inlined variables and all that.

What's weird is in C++14 and C++11 gcc rejects this stating m is used but never defined, while clang accepts it. Live

Is m odr-used? Or is this a gcc bug?

2 Answers

This is not a GCC bug. This is a interpretation of the c++14.

One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries). The compiler is not required to diagnose this violation, but the behavior of the program that violates it is undefined.

The error is

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]

And it is true that the type foo()::<lambda(int)> is never defined (by the programmer, it is actually defined by the compiler) since every lambda has a unique type.

replacing line static constexpr void (*m) (T, C) = &g; by static inline constexpr void (*m) (T, C) = &g; makes the error go away.

Which is clearly an indication that m is odr-used if not tag as inline.

I believe this message is a way of warning you that

auto l = [](int){return 42;};
G<int, decltype(l)>::m(420, l);
G<int, decltype(l)>::m(420, [](int){return 42;});

will result in the following error (even with using -std=c++1z and GCC or 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);

because the lambda types are unique.

However GCC transforms the error into a warning if you use -fpermissive which is really a way of knowing that it is not a GCC bug but an over-protection meant to discourage certain practices.

One way of lifting any ambiguity without using -fpermissive is to do what GCC recommends and declare the prototype.

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);
}

This compile fines.

Last, why did the GCC team make this protection pop with C++17. I don't know maybe they receive complaints that the behavior was over protective and that cast errors of lambda types were sufficient. But seriously the error

error: could not
    convert 'g' from 'foo()::<lambda(int)>' to 'foo()::<lambda(int)>'

Is a little bit off-putting the first time… When getting this error your mind wonders, until you come to the conclusion that decltype([](int){return 42;}) is different from decltype([](int){return 42;}) ! When you try this...

#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;
}

which outputs (under visual studio)

class <lambda_37799c61f9e31cc7b5f51a1bd0a09621>
class <lambda_818eb0a43a553fc43d3adadd7480d71e>

TLDR m isn't odr-used, this is indeed a GCC bug.

odr-use

Intuitively, variables needs to be stored in memory somewhere. The only exception is when the value of the variable can be optimized out by the compiler and never used in another way. Odr-usage formalizes this idea: a variable requires a definition only if it is odr-used.

Odr-usage is defined by [basic.def.odr]

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion is applied to e, or e is a discarded-value expression.

In other words, x isn't odr-used if either

  1. x doesn't appear as a potentially-evaluated expression (e.g. decltype(x)).
  2. x isn't an object (e.g. a reference) and applying the lvalue-to-rvalue conversion to x yields a constant expression.
  3. x is an object and applying the lvalue-to-rvalue conversion to x yields a constant expression. Furthermore, there is a "coupled" expression e that either has the lvalue-to-rvalue conversion applied to it or is discarded.

The "coupling" refers to the intuition that there is some enclosing expression closely related to x that can only be used in certain ways without needing x be stored in memory. This notion is formalized by the definition of potential results [basic.def.odr]

The set of potential results of an expression e is defined as follows:

  • If e is an id-expression, the set contains only e.

  • If e is a subscripting operation with an array operand, the set contains the potential results of that operand.

  • If e is a class member access expression, the set contains the potential results of the object expression.

  • If e is a pointer-to-member expression whose second operand is a constant expression, the set contains the potential results of the object expression.

  • If e has the form (e1), the set contains the potential results of e1.

  • If e is a glvalue conditional expression, the set is the union of the sets of potential results of the second and third operands.

  • If e is a comma expression, the set contains the potential results of the right operand.

  • Otherwise, the set is empty.

An expression ex that is within the potential results of e is "coupled" with e.

The question

Applying the definitions

  1. m is potentially evaluated.
  2. Applying the lvalue-to-rvalue conversion to m yields a constant expression.
  3. m is an object.
  4. m is an expression whose potential results includes m and has the lvalue-to-rvalue conversion applied to.

We thus conclude m isn't odr-used.

Related