Reputation: 93264
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; });
^
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)};
}
...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?
Upvotes: 5
Views: 465
Reputation: 137301
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>;
Upvotes: 7
Reputation: 93264
I found a possible solution, but it does require an external implementation of a make
function.
Good news: it doesn't have to be make_node
- it can work with any type T
that supports class template argument deduction.
template <template <typename...> class T, typename... Ts>
auto make(Ts&&... xs)
noexcept(noexcept(T{std::forward<Ts>(xs)...}))
-> decltype(T{std::forward<Ts>(xs)...})
{
return T{std::forward<Ts>(xs)...};
}
Usage:
template <typename FThen>
auto then(FThen&& f_then)
{
return make<node>([f_then = std::move(f_then)]
{
return f_then();
});
}
Upvotes: 0