In the following code, I wanted to achieve the effect of binary operator chaining for the specific type definition I want to use - for trivial operator chaining, that a binary operator returns the same type of object, most of cases just simply returning *this
, which could trivially be used again to chain the next object of the same type.
However, in my case, the binary operators takes two reference_wrappers of two same typed objects (awaitable<T>::ref
) as input, and returns an aggregated object of type (awaitable<awaitable<T>::ref>
), and I wanted to use the returned aggregate object to chain the next awaitable<T>::ref
and again return the further aggregated object of type awaitable<awaitable<T>::ref>
- notice that the returned object is always the same type of awaitable<awaitable<T>::ref>
, no matter how many more chaining happens.
The friend operator with template template parameter defined at location (XXX) hoped to serve this purpose, but compilers don't seem to be willing to perform the bind.
Could anyone shed some lights on how I can achieve the result as described?
Thanks!
#include <functional>
template <typename T>
struct awaitable
{
typedef std::reference_wrapper<awaitable> ref;
// (A) - okay
friend awaitable<ref> operator||(ref a1, ref a2)
{
awaitable<ref> r;
return r;
}
// (XXX) - this doesn't bind
template < template <typename> class _awaitable >
friend awaitable<ref> operator||(typename awaitable<typename _awaitable<T>::ref>::ref a1, ref a2)
{
awaitable<ref> r;
return r;
}
};
int main(int argc, const char * argv[])
{
awaitable<void> a1;
awaitable<void> a2;
auto r1 = a1 || a2; // Okay - r1 is of type awaitable<awaitable<void>::ref>
awaitable<void> a3;
auto r3 = r1 || a3; // doesn't bind to the operator defined at XXX
return 0;
}
[EDIT] -
Answers in this post and this seem to explain the situation pretty nicely, but in my case, the friend operator has a template template parameter (which is needed to avoid recursive template instantiation), which might prevent the compilers to generate the correct namespace scope function when the template is instantiated?
It seems that the friend function with template template parameter makes template deduction fail. The solution is to remove the template template parameter, and expand the ::ref usage to std::reference_wrapper within the friend function definition:
#include <functional>
template <typename T>
struct awaitable
{
typedef std::reference_wrapper<awaitable> ref;
// (A) - okay
friend awaitable<ref> operator||(ref a1, ref a2)
{
awaitable<ref> r;
return r;
}
// (XXX) - removing the template template parameter makes the template instantiation for a specific type T to generate a namespace version of the function!
friend awaitable<ref> operator||(std::reference_wrapper<awaitable<std::reference_wrapper<awaitable<T>>>> a1, ref a2)
{
awaitable<ref> r;
return r;
}
};
// template <typename T>
// awaitable<typename awaitable<T>::ref> operator||(typename awaitable<typename awaitable<T>::ref>::ref a1, typename awaitable<T>::ref a2)
// {
// awaitable<typename awaitable<T>::ref> r;
// return r;
// }
int main(int argc, const char * argv[])
{
awaitable<void> a1;
awaitable<void> a2;
auto r1 = a1 || a2; // Okay - r1 is of type awaitable<awaitable<void>::ref>
awaitable<void> a3;
auto r3 = r1 || a3; // now it works!
return 0;
}
Demo here