2

I was trying to implement a certain type trait, but faced this following error.

Here is a simple program that shows the behavior:

#include <iostream>

template<typename T>
struct my_floating_point // just to make a case
{
    // static constexpr bool value = std::is_floating_point_v<T>;
    // THIS WORKS

    static constexpr bool value = []()
    {
        if constexpr(std::is_floating_point_v<T>) { return true; }
        else { return false; }
    }; // compilation error
};

int main()
{
    std::cout << my_floating_point<int>::value << std::endl;
    std::cout << my_floating_point<double>::value << std::endl;
    std::cout << my_floating_point<char>::value << std::endl;
}

The commented line works just fine. However, the uncommented piece of code gives this warning with g++:

g++ -std=c++17 -O3 main.cpp -Wall -Wextra -pedantic
main.cpp: In instantiation of ‘constexpr const bool my_floating_point<int>::value’:
main.cpp:18:39:   required from here
main.cpp:9:24: warning: the address of ‘static constexpr bool my_floating_point<int>::<lambda()>::_FUN()’ will never be NULL [-Waddress]
  static constexpr bool value = []()
                        ^~~~~
main.cpp:9:24: warning: the address of ‘static constexpr bool my_floating_point<int>::<lambda()>::_FUN()’ will never be NULL [-Waddress]

And running it outputs

1
1
1

What is wrong?

Note: clang++ for some reason doesn't give warning, but outputs the same.

Bbllaaddee
  • 145
  • 1
  • 9

2 Answers2

3
 []()
    {
      // ...
    }

A lambda expression evaluates to an instance of an anonymous type. That's what a lambda really is: an object whose type is unspecified. Attempting to assign an instance of this object to a bool is not valid C++.

The good news is that this anonymous type has an operator() overload, that just happens, accidentally, to return a bool. So, for example, this will work:

static constexpr bool value = []()
{
    if constexpr(std::is_floating_point_v<T>) { return true; }
    else { return false; }
}(); // compiles just fine.
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 2
    It is valid code because stateless lambda implicitly converts to pointer to free function, which implicitly converts to bool (with value `true`). The OP compiler is being good QoI in warning that this is probably not what was intended – M.M Jul 25 '21 at 22:06
3

Summary: Simplify to get a more readable warning message, then compare the message to a possibly better-recognized case. This is more of a work-through answer (teach to fish) than a direct explanation (give a fish).


This is one of the cases where really working at a minimal reproducible example can have benefits. I acknowledge that some of the complication was kept "just to make a case", but let's drop that for now.

The three lines in main each trigger the same warning, so simplify to just one of those lines. Preferably, we keep a line whose output is unexpected, such as the first of the three lines (int is not a floating point type, so my_floating_point<int>::value is intended to be false). That cuts the compiler's messages to a third of what they were and helps identify which messages go together (at this point, they all go together).

With only one type to consider, we no longer need to my_floating_point to be a template. Templates sometimes add a significant amount of insignificant detail to warnings (such as – in this case – the required from here message), so drop the template and replace std::is_floating_point_v<T> with std::is_floating_point_v<int>. This reduces the warning down to one message plus the indication of where the warning occurred.

One more simplification: take value outside the struct, allowing my_floating_point to be eliminated. Admittedly, this does not have much impact on the error message, but it does remove a red herring from the example code.

#include <iostream>

static constexpr bool value = []()
{
    if constexpr(std::is_floating_point_v<int>) { return true; }
    else { return false; }
}; // compilation warning

int main()
{
    std::cout << value << std::endl;
}
prog.cc:7:1: warning: the address of 'static constexpr bool<lambda()>::_FUN()' will never be NULL [-Waddress]
    7 | }; // compilation warning
      | ^

Note that this is almost one of the lines from the question's compiler output. The only difference is the removal of my_floating_point<int>:: which corresponds nicely with the simplifications we did.


OK, now that we have the situation suitably simplified, let's compare the warning to one produced in a slightly different setup. Instead of a lambda, let's use an official function. The goal is to gain some basic (yet imprecise) understanding by by comparing similar scenarios.

Don't get hung up on "how would I think to do this?" This is something I knew to do because I recalled a remarkably similar warning from a different setup. The first part of this answer presented steps that I think are reasonable to expect from others. In contrast, this part of this answer requires familiarity with messages from the compiler.

#include <iostream>

bool fun()
{
    if constexpr(std::is_floating_point_v<int>) { return true; }
    else { return false; }
}

static constexpr bool value = fun; // compilation warning

int main()
{
    std::cout << value << std::endl;
}
prog.cc:9:31: warning: the address of 'bool fun()' will never be NULL [-Waddress]
    9 | static constexpr bool value = fun; // compilation warning
      |                               ^~~

Again, the same warning that the address of [something] will never be NULL. (Perhaps you already see where this is going?) In this code, since the function is not invoked, the symbol fun becomes a pointer to the function. Assigning a pointer to a bool results in true if and only if the pointer is non-null. This particular pointer cannot be null because it is the address of something. So the compiler throws out a warning; the code is syntactically valid, but you probably meant to invoke the function (by adding parentheses just before the semicolon, as in fun() instead of just fun).

Similar mechanics are in play for the code with a lambda. When the lambda is not invoked, it can convert to a bool, becoming true if and only if there is a lambda. As in the function case, the result has to be true. The compiler throws out a warning; the code is syntactically valid, but you probably meant to invoke the lambda (by adding parentheses just before the semicolon).

static constexpr bool value = []()
{
    if constexpr(std::is_floating_point_v<int>) { return true; }
    else { return false; }
}(); // no compilation warning with the parentheses

Even better, the output is now 0, as intended.


OK, the short version is "add parentheses", but the real lesson of this answer is the usefulness of recognizing compiler messages. When a warning is worded similarly to one you've encountered before, try to replicate the warning you are familiar with. See if there are similarities that allow you to transfer a solution from the familiar case to the less familiar one.

Familiarity takes time, but it takes less time if you make it a goal. ;)

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • For those who really want to simplify: the lambda could be reduced to `[]() { return false; }`. However, that would make this answer even longer, and I don't see enough benefit to justify that. (The warning does not change.) – JaMiT Jul 26 '21 at 01:16
  • Nice technique! I always get confused after some time by the compiler messages.. Thanks! – Bbllaaddee Jul 26 '21 at 11:34