Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return type deduction for methods of nested classes

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.

like image 860
mibu Avatar asked Dec 24 '21 16:12

mibu


People also ask

What is type deduction c++?

Type inference or deduction refers to the automatic detection of the data type of an expression in a programming language. It is a feature present in some strongly statically typed languages. In C++, the auto keyword(added in C++ 11) is used for automatic type deduction.

What is template argument deduction in C++?

Class Template Argument Deduction (CTAD) is a C++17 Core Language feature that reduces code verbosity. C++17's Standard Library also supports CTAD, so after upgrading your toolset, you can take advantage of this new feature when using STL types like std::pair and std::vector.

Can class template be nested?

Member templates that are classes are referred to as nested class templates. Member templates that are functions are discussed in Member Function Templates. Nested class templates are declared as class templates inside the scope of the outer class. They can be defined inside or outside of the enclosing class.

What types can be used in a nested class declaration?

Declarations in a nested class can use only type names, static members, and enumerators from the enclosing class. Declarations in a nested class can use any members of the enclosing class, following the usual usage rules for the non-static members.

What is a nested class in C++?

Although enumerations are probably the most common type that is nested inside a class, C++ will let you define other types within a class, such as typedefs, type aliases, and even other classes! Like any normal member of a class, nested classes have the same access to members of the enclosing class that the enclosing class does.

Can a static class have a nested method?

And like static class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class: it can use them only through an object reference. They are accessed using the enclosing class name. For example, to create an object for the static nested class, use this syntax:

What is the scope of a nested class?

The scope of a nested class is bounded by the scope of its enclosing class. Thus in above example, class NestedClass does not exist independently of class OuterClass.


Video Answer


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

like image 83
Klaus Avatar answered Nov 15 '22 04:11

Klaus