Return type deduction for methods of nested classes

Viewed 95

I encountered a problem relating to return type deduction for methods of nested classes. After some digging, I narrowed down the problem to a simplified version as following:

#include <type_traits>

struct Alive {};

template<typename T>
auto is_alive_impl(...) -> void;

template<typename T>
auto is_alive_impl(int) -> decltype(std::declval<T>().operator()());

template<typename T>
struct is_alive : std::is_same<decltype(is_alive_impl<T>(0)), Alive> {};

struct Box {
    struct Cat {
        auto 
        operator()() {
            return Alive{};
        }
    };

    static constexpr auto value = is_alive<Cat>::value; // (1)
};

int main() {
    static_assert(is_alive<Box::Cat>::value); // (2)
}

Compiler Explorer link

struct is_alive check if a class has operator() which return an Alive instance using function overloading trick.

Without line (1), the static_assert at (2) return true and the code compiles as expected.

However, I do not understand why adding line (1) make line (2) failed. It seems like I looked into the box and somehow killed the cat.

So here is my questions:

  1. Why at line (1), value is false?
  2. Why adding line (1) make line (2) failed?

It would be great if you can refer to parts of the Standard which dictate that behaviors.

1 Answers

You are running all your code more or less in SFINAE context and so you don't see any problem. If you simply try to instantiate your class by

int main() { Box::Cat bc; }

we see all the error messages.

What happens:

If you want inside the class define ( and not only declare! ) a variable which needs to deduce the return type of your operator() the statement fails as auto can not be deduced until the class is defined. ( by the end of the class definition with the close bracket. ) And this also lets fail the std::declval<T>() as the auto value = is_alive<Cat>::value will not work. And as we are in SFINAE context and the substitution silently fails, we run in the default template auto is_alive_impl(...) -> void;. Bammm :-)

If we add a bit more indirection and put the cat in a smaller box, we can run the code:

struct LittleBox
{
    struct Cat {
        auto
        operator()()  {
            return Alive{};
        }
    };  

};

struct Box: public LittleBox {

    static constexpr  auto value = is_alive<Cat>::value; // (1)
};

OK, much simpler we can define the return type of the operator()() and don't use auto here.

The fix or work around seems to work on gcc/msvc/clang:explore

Related