We consider the following functor, to help to detect what version of of its operator()
is called.
struct functor
{
void operator()() {std::cout << "functor::operator" << std::endl;}
void operator()() const {std::cout << "functor::operator const" << std::endl;}
void operator()() volatile {std::cout << "functor::operator volatile" << std::endl;}
void operator()() const volatile {std::cout << "functor::operator const volatile" << std::endl;}
void operator()(int) & {std::cout << "functor::operator &" << std::endl;}
void operator()(int) const& {std::cout << "functor::operator const&" << std::endl;}
void operator()(int) volatile& {std::cout << "functor::operator volatile&" << std::endl;}
void operator()(int) const volatile& {std::cout << "functor::operator const volatile&" << std::endl;}
void operator()(int) && {std::cout << "functor::operator &&" << std::endl;}
void operator()(int) const&& {std::cout << "functor::operator const&&" << std::endl;}
void operator()(int) volatile&& {std::cout << "functor::operator volatile&&" << std::endl;}
void operator()(int) const volatile&& {std::cout << "const volatile&&" << std::endl;}
};
I would like to know, in C++17
(if doable of course) how to write two functor wrappers storing the function in a tuple and using class template argument deduction:
wrapper1
that will call the operator depending on the qualifiers of the functorwrapper2
that will call the operator depending on the qualifiers of the wrapperFor example:
wrapper1 w1(functor{});
wrapper2 w2(functor{});
w1(0); // should call the && version since the functor was built as a temporary
w2(0); // should call the & version since the wrapper was built as a value
Here are some very rough exploration of the kind of things I am looking for. Note: this is just to give a "taste" of what I am thinking of.
template <class F>
struct wrapper_a
{
constexpr wrapper_a(F f) noexcept: _tuple(f) {}
template <class... Args>
void operator()(Args&&... args)
{std::get<0>(_tuple)(std::forward<Args>(args)...);}
std::tuple<F> _tuple;
};
template <class F>
struct wrapper_b
{
template <class G>
constexpr wrapper_b(G&& g) noexcept: _tuple(std::forward<G>(g)) {}
template <class... Args>
void operator()(Args&&... args)
{std::get<0>(_tuple)(std::forward<Args>(args)...);}
std::tuple<F> _tuple;
};
template <class F> wrapper_b(F&& f) -> wrapper_b<F>;
template <class F>
struct wrapper_c
{
template <class G>
constexpr wrapper_c(G&& g) noexcept: _tuple(std::forward<G>(g)) {}
template <class... Args>
void operator()(Args&&... args)
{std::forward<F>(std::get<0>(_tuple))(std::forward<Args>(args)...);}
std::tuple<F> _tuple;
};
template <class F> wrapper_c(F&& f) -> wrapper_c<F>;
How to achieve what I am looking for? Is that even possible?
Not sure to understand your exactly requirements... and I'm a rookie with reference forwarding... anyway
The first one wrapper1 that will call the operator depending on the qualifiers of the functor
Maybe an expert can avoid this complication but seems to me that you need two template parameters
template <typename F, typename G>
struct wrapper1
where F
is the type of the copy of the parameter passed to the constructor and G
is the intended type.
So you have in it a F
value
F f;
and you can use it trough forwarding
template <typename ... Args>
void operator() (Args && ... as) &
{ std::forward<G>(f)(std::forward<Args>(as)...); }
To simplify, you can define a template deduction as follows
template <typename F>
wrapper1(F && f) -> wrapper1<std::decay_t<F>, decltype(std::forward<F>(f))>;
The second one wrapper2 that will call the operator depending on the qualifiers of the wrapper
Maybe I'm wrong but this seems to me a littler simpler.
You need only a template parameter
template <typename F>
struct wrapper2
and you only have to use std::move()
in r-reference operators
template <typename ... Args>
void operator() (Args && ... as) &&
{ std::move(f)(std::forward<Args>(as)...); }
The deduction guide is simple
template <typename F>
wrapper2(F && f) -> wrapper2<std::decay_t<F>>;
The following is a limited (to const
/not-const
and l-value/r-value reference alternatives) but full working examples.
Observe that there are some compilation errors caused by constness problem (if you initialize a const wrapper1
with a not-const
callable, there are problems)
#include <iostream>
struct functor
{
void operator()(int) &
{std::cout << "functor::operator &" << std::endl;}
void operator()(int) const &
{std::cout << "functor::operator const &" << std::endl;}
void operator()(int) &&
{std::cout << "functor::operator &&" << std::endl;}
void operator()(int) const &&
{std::cout << "functor::operator const &&" << std::endl;}
};
template <typename F, typename G>
struct wrapper1
{
template <typename H>
constexpr wrapper1 (H && f0) noexcept : f{std::forward<H>(f0)}
{}
template <typename ... Args>
void operator() (Args && ... as) &
{ std::forward<G>(f)(std::forward<Args>(as)...); }
template <typename ... Args>
void operator() (Args && ... as) const &
{ std::forward<G>(f)(std::forward<Args>(as)...); }
template <typename ... Args>
void operator() (Args && ... as) &&
{ std::forward<G>(f)(std::forward<Args>(as)...); }
template <typename ... Args>
void operator() (Args && ... as) const &&
{ std::forward<G>(f)(std::forward<Args>(as)...); }
F f;
};
template <typename F>
wrapper1(F && f)
-> wrapper1<std::decay_t<F>, decltype(std::forward<F>(f))>;
template <typename F>
struct wrapper2
{
template <typename H>
constexpr wrapper2 (H && f0) noexcept : f{std::forward<H>(f0)}
{}
template <typename ... Args>
void operator() (Args && ... as) &
{ f(std::forward<Args>(as)...); }
template <typename ... Args>
void operator() (Args && ... as) const &
{ f(std::forward<Args>(as)...); }
template <typename ... Args>
void operator() (Args && ... as) &&
{ std::move(f)(std::forward<Args>(as)...); }
template <typename ... Args>
void operator() (Args && ... as) const &&
{ std::move(f)(std::forward<Args>(as)...); }
F f;
};
template <typename F>
wrapper2(F && f) -> wrapper2<std::decay_t<F>>;
int main ()
{
functor fc;
functor const fd;
wrapper1 w1a{functor{}};
wrapper1 w1b{static_cast<functor const &&>(functor{})};
wrapper1 w1c{fc};
wrapper1 w1d{fd};
wrapper1 const w1e{functor{}};
wrapper1 const w1f{static_cast<functor const &&>(functor{})};
wrapper1 const w1g{fc};
wrapper1 const w1h{fd};
w1a(0);
w1b(0);
w1c(0);
w1d(0);
std::cout << "----------------------------" << std::endl;
// w1e(0); // compilation error
w1f(0);
// w1g(0); // compilation error
w1h(0);
std::cout << "----------------------------" << std::endl;
wrapper1<functor, functor&&>{functor{}}(0);
wrapper1<functor, functor const &&>
{static_cast<functor const &&>(functor{})}(0);
wrapper1<functor, functor&>{fc}(0);
wrapper1<functor, functor const &>{fd}(0);
std::cout << "----------------------------" << std::endl;
// (wrapper1 <functor, functor&&> const)
//{functor{}}(0); // compilation error
(wrapper1<functor, functor const &&> const)
{static_cast<functor const &&>(functor{})}(0);
// (wrapper1<functor, functor&> const){fc}(0); // compilation error
(wrapper1<functor, functor const &> const){fd}(0);
wrapper2 w2a{functor{}};
wrapper2 w2b{static_cast<functor const &&>(functor{})};
wrapper2 w2c{fc};
wrapper2 w2d{fd};
wrapper2 const w2e{functor{}};
wrapper2 const w2f{static_cast<functor const &&>(functor{})};
wrapper2 const w2g{fc};
wrapper2 const w2h{fd};
std::cout << "----------------------------" << std::endl;
w2a(0);
w2b(0);
w2c(0);
w2d(0);
std::cout << "----------------------------" << std::endl;
w2e(0);
w2f(0);
w2g(0);
w2h(0);
std::cout << "----------------------------" << std::endl;
wrapper2<functor>{functor{}}(0);
wrapper2<functor>{static_cast<functor const &&>(functor{})}(0);
wrapper2<functor>{fc}(0);
wrapper2<functor>{fd}(0);
std::cout << "----------------------------" << std::endl;
(wrapper2<functor> const){functor{}}(0);
(wrapper2<functor> const){static_cast<functor const &&>(functor{})}(0);
(wrapper2<functor> const){fc}(0);
(wrapper2<functor> const){fd}(0);
}