Search code examples
c++templatesc++17template-argument-deduction

Template argument type deduction from within the class definition


Is it possible to use class template argument deduction for a class C from within the definition of one of C's member functions? ...or am I forced to write my make_c helper class like in C++03?

Consider this minimized and simplified scenario that builds a chain of arbitrary function objects:

template <typename F>
struct node;

template <typename FFwd>
node(FFwd&&) -> node<std::decay_t<FFwd>>;

The node class stores a function object which is initialized via perfect-forwarding. I need a deduction guide here to decay the type of the function object.

template <typename F>
struct node
{
    F _f;

    template <typename FFwd>
    node(FFwd&& f) : _f{std::forward<FFwd>(f)}
    {
    }

    template <typename FThen>
    auto then(FThen&& f_then)
    {
        return node{[f_then = std::move(f_then)]
                    { 
                        return f_then(); 
                    }};
    }
};

Afterwards, I define node's constructor and a .then continuation member function that returns a new node (its implementation is nonsensical to minimize the size of the example). If I attempt to invoke .then...

auto f = node{[]{ return 0; }}.then([]{ return 0; });

...I get an unexpected compilation error:

prog.cc: In instantiation of 'node<F>::node(FFwd&&) [with FFwd = node<F>::then(FThen&&) [with FThen = main()::<lambda()>; F = main()::<lambda()>]::<lambda()>; F = main()::<lambda()>]':
prog.cc:27:22:   required from 'auto node<F>::then(FThen&&) [with FThen = main()::<lambda()>; F = main()::<lambda()>]'
prog.cc:35:56:   required from here
prog.cc:17:46: error: no matching function for call to 'main()::<lambda()>::__lambda1(<brace-enclosed initializer list>)'
     node(FFwd&& f) : _f{std::forward<FFwd>(f)}
                                              ^
prog.cc:35:20: note: candidate: 'constexpr main()::<lambda()>::<lambda>(const main()::<lambda()>&)'
     auto f = node{[]{ return 0; }}.then([]{ return 0; });
                    ^

live example on wandbox

This happens because inside the body of node<F>::then, node{...} creates an instance with the type of *this - it doesn't trigger argument type deduction. I am therefore forced to write:

template <typename FThen>
auto then(FThen&& f_then)
{
    auto l = [f_then = std::move(f_then)]{ return f_then(); };
    return node<std::decay_t<decltype(l)>>{std::move(l)};
}

live example on wandbox

...which defeats the whole purpose of the deduction guide.

Is there a way I can use class template argument deduction here without introducing code repetition or a make_node function?


Solution

  • The name lookup for node found the injected-class-name, from which deduction is not performed. (Performing deduction in this case would have been a backward compatibility break.)

    If you want deduction, qualify the name so that you find the namespace member.

    template <typename FThen>
    auto then(FThen&& f_then)
    {
        return ::node{[f_then = std::move(f_then)]
        //     ^^
                    { 
                        return f_then(); 
                    }};
    }
    

    Also, a cleaner way to write the guide is

    template <typename F>
    node(F) -> node<F>;