4

I encountered an unusual pair of compiler warnings that seem mutually contradictory. Here's the code I've written:

#include <functional>
#include <iostream>

namespace {
  std::function<void()> callback = [] {
    void theRealCallback(); // Forward-declare theRealCallback
    theRealCallback();      // Invoke theRealCallback
  };

  void theRealCallback() {
    std::cout << "theRealCallback was called." << std::endl;
  }
}

int main() {
  callback();
}

In other words, I define a lambda function in an unnamed namespace that forward-declares a function defined later in that unnamed namespace.

When I compile this code with g++ 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1), I get these two warnings:

CompilerWarningsNamespace.cpp:6:10: warning: ‘void {anonymous}::theRealCallback()’ used but never defined
     void theRealCallback();
          ^~~~~~~~~~~~~~~
CompilerWarningsNamespace.cpp:10:8: warning: ‘void {anonymous}::theRealCallback()’ defined but not used [-Wunused-function]
   void theRealCallback() {
        ^~~~~~~~~~~~~~~

This is strange, because the first warning says that I'm using a function without defining it, and the second warning says that I'm defining a function without using it.

Running this program indeed produces the output

theRealCallback was called.

and the program terminates normally.

Interestingly, these warnings go away if instead of using an unnamed namespace, I give the namespace a name, as shown here:

#include <functional>
#include <iostream>

namespace NamedNamespace {
  std::function<void()> callback = [] {
    void theRealCallback();
    theRealCallback();
  };

  void theRealCallback() {
    std::cout << "theRealCallback was called." << std::endl;
  }
}

int main() {
  NamedNamespace::callback();
}

The modified version of the code, like the original code, prints out a message indicating that theRealCallback was invoked.

Can someone explain why I'm getting these warnings? My guess is that this has something to do with the forward-declaration of the function in the lambda function as being interpreted as something other than the later function that appears in the unnamed namespace, but if that's the case I'm not sure I see why this links in the end and why I'm getting those warnings.

templatetypedef
  • 328,018
  • 92
  • 813
  • 992
  • You can not forward declare thetheRealCallback() inside of the lambda. Move it above and it should work. – lakeweb Jan 31 '20 at 01:26
  • I'll be back in the morning. Your post is most interesting. The heavies will chime in and straighten this out. I have a feeling your compiler is being forgiving of what it should not. But without doing the lawyer thing... Good night. – lakeweb Jan 31 '20 at 02:42

1 Answers1

4

I think the open CWG issue 2058 is relevant here to decide whether your program is well-formed.


By the current wording of the standard, I think your program is ill-formed.

Based on the C++17 standard (final draft):

According to [basic.link]/6 the declaration in your lambda will declare theRealCallback with external linkage, because it is a function declaration at block scope that doesn't match to some other, already declared, entity.

At the same time according to [basic.link]/4.2 the second declaration of theRealCallback has internal linkage because it is a namespace scope declaration of a function in an unnamed namespace.

According to [basic.link]/6 a program is ill-formed if it declares an entity with both internal and external linkage in the same translation unit. This ill-formedness was added only recently though, as resolution of CWG issue 426.


As mentioned in the notes of CWG issue 426, according to [basic.link]/9, the declarations only refer to the same entity though if they have the same linkage, meaning the ill-formedness condition in its resolution doesn't apply.

So if we interpret this strictly, then the program really has two independent functions void theRealCallback(), one with external and one with internal linkage. The one with internal linkage has a definition, but the one with external linkage does not. If that is the case the program violates the one-definition rule, because the call theRealCallback(); in the lambda ODR-uses the function with external linkage which doesn't have a definition. This would make the program ill-formed, no diagnostic required.

While this is probably the correct interpretation by strict reading of the standard, I think that the resolution of CWG issue 426 was meant to apply here, because with the interpretation above it wouldn't ever apply. I don't know why the mentioned issue wasn't fixed in the resolution.


Should CWG issue 2058 be resolved to say that the declaration at block scope will match the linkage of the enclosing namespace, then the program would be well-formed and theRealCallback would have internal linkage.


You can see that GCC considers the program to be ill-formed if the standard is interpreted strictly by adding the -pedantic-errors flag, which will cause it to emit an error rather than a warning.


Of course the simplest fix to work around this issue is to forward declare void theRealCallback(); outside the lambda at namespace scope, in which case linkage is definitely internal and any block scope declaration would refer to that declaration, taking on its linkage.


This is not an issue if the namespace is named, because then the namespace scope declaration will have external linkage as well.

walnut
  • 20,566
  • 4
  • 18
  • 54
  • I came to a similar conclusion, except that the names don't actually denote the same function, per [\[basic.link\]/10](https://timsong-cpp.github.io/cppwp/basic.link#10). So the rule in [basic.link]/7 doesn't apply; it's just an ODR violation. A note on issue 426 points this out, but it seems that point was never resolved. – aschepler Jan 31 '20 at 03:36
  • @aschepler I have added a paragraph about that. Please let me know if this differs from what you concluded. – walnut Jan 31 '20 at 03:57
  • Thanks for the clarification. You saved me a lot of homework. – lakeweb Jan 31 '20 at 14:47