Note: I am working with VS2013 so the C++11 features available are limited.
I am having trouble with overloading a template function depending on if the argument type is callable or not and, ideally, if the arguments match a specific pattern.
Here is a very simplified example of the code I have, my problem being how to implement update_callabe()
overloads :
template< class T, class... Args >
void update_callable( const std::vector<T>& objects, Args&&... args ); // 1: How to implement this?
template< class T, class... UpdateArgs>
class Controller
{ //...
virtual void update( T&, UpdateArgs... args ) = 0;
public:
template< class IterBegin, class IterEnd, class... Args >
void update_batch( IterBegin first, IterEnd last, Args&&... args )
{
std::for_each( first, last, [&]( T& object ){ update(object, args...); }
}
//...
};
template< class T, class... UpdateArgs >
class Group
{
public:
using ControllerType = Controller<T,UpdateArgs...>;
void add( ControllerType& controler ) { /* ... */ m_controllers.emplace_back( &controller ); }
template< class... Args >
void update( Args&&... args )
{
update_callable(m_objects, std::forward<Args>(args)); // 2
for( auto* controller : m_controllers )
{
controller->update_batch( begin(m_objects), end(m_objects), std::forward<Args>(args)); // 3
}
}
private:
std::vector<T> m_objects;
std::vector<ControllerType*> m_controllers;
//...
};
A. What I want to achieve with update_callabe()
overloads (in priority order):
B. That would be ok for me, but ideally I would like update_callabe()
overloads that follow these rules (in priority order):
I have tried with enable_if, conditional and several advanced techniques but I'm no expert (yet) so I'm failing to express this correctly.
Some notes about the example here:
You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.
A function template can overload non-template functions of the same name. In this scenario, the compiler first attempts to resolve a function call by using template argument deduction to instantiate the function template with a unique specialization.
Both function overloading and templates are examples of polymorphism features of OOP. Function overloading is used when multiple functions do quite similar (not identical) operations, templates are used when multiple functions do identical operations.
You overload a function name f by declaring more than one function with the name f in the same scope. The declarations of f must differ from each other by the types and/or the number of arguments in the argument list.
When I want if
/else if
/else
-like behavior at compile time, I use a trick like this:
template <unsigned int N>
struct priority_helper
: public priority_helper<N-1> {};
template <>
struct priority_helper<0U> {};
template <unsigned int N>
using priority = int priority_helper<N>::*;
constexpr priority<0> by_priority{};
template <typename Arg>
auto do_thing_detail(Arg&& arg, priority<1>)
-> typename std::enable_if<cond1<Arg>::value>::type
{ /*...*/ }
template <typename Arg>
auto do_thing_detail(Arg&& arg, priority<2>)
-> typename std::enable_if<cond2<Arg>::value>::type
{ /*...*/ }
template <typename Arg>
void do_thing_detail(Arg&& arg, priority<3>)
{ /*...*/ }
template <typename Arg>
void do_thing(Arg&& arg)
{ do_thing_detail(std::forward<Arg>(arg), by_priority); }
This would also work using the simpler types priority_helper<N>*
instead of int priority_helper<N>::*
, but then the larger values of N
would be the preferred overloads, since pointer-to-derived is more specific than pointer-to-base. By using the pointer to member, the implicit conversions and therefore overload preferences go the other way around (pointer-to-member-of-base converts to pointer-to-member-of-derived).
So for your problem, after defining priority<N>
as above...
template < class T, class... Args >
auto update_callable_detail(
priority<1>,
const std::vector<T>& objects,
Args&& ... args )
-> decltype(std::declval<const T&>()(std::forward<Args>(args)...), void())
{
for ( const T& obj : objects )
obj( std::forward<Args>(args)... );
}
template < class T, class... Args >
auto update_callable_detail(
priority<2>,
const std::vector<T>& objects,
Args&& ... )
-> decltype(std::declval<const T&>()(), void())
{
for ( const T& obj : objects )
obj();
}
template < class T, class... Args >
void update_callable_detail(
priority<3>,
const std::vector<T>&,
Args&& ... )
{
}
template < class T, class... Args >
void update_callable( const std::vector<T>& objects, Args&& ... args )
{
update_callable_detail( by_priority, objects, std::forward<Args>(args)... );
}
In this case it just seemed simpler to use SFINAE directly in the overload declarations, rather than do anything with std::result_of
(especially since the C++11 requirements for result_of
aren't as helpful as the C++14 version). Whenever the deduced arguments for T
and Args
result in an illegal expression in the decltype
, that overload is thrown out during overload resolution.
Doable with a couple of traits and some tag dispatching (Demo at Coliru). First, define traits that determine if T
is either callable with the specified argument types:
template <typename T, typename... Args>
struct callable_with_args_ {
template <typename U=T>
static auto test(int) ->
decltype((void)std::declval<U>()(std::declval<Args>()...), std::true_type());
static auto test(...) -> std::false_type;
using type = decltype(test(0));
};
template <typename T, typename... Args>
using callable_with_args = typename callable_with_args_<T, Args...>::type;
or with no arguments:
template <typename T>
struct callable_without_args_ {
template <typename U=T>
static auto test(int) ->
decltype((void)std::declval<U>()(), std::true_type());
static auto test(...) -> std::false_type;
using type = decltype(test(0));
};
template <typename T>
using callable_without_args = typename callable_without_args_<T>::type;
Then implement a two-level tag dispatch to get the precedence that you want:
template < class T >
void update_callable_no_args(std::false_type, const std::vector<T>&) {}
template < class T >
void update_callable_no_args(std::true_type, const std::vector<T>& objects) {
for (auto&& i : objects) {
i();
}
}
template< class T, class... Args >
void update_callable_args(std::false_type,
const std::vector<T>& objects,
Args&&... ) {
update_callable_no_args(callable_without_args<T const&>(), objects);
}
template< class T, class... Args >
void update_callable_args(std::true_type,
const std::vector<T>& objects,
Args&&... args ) {
for (auto&& i : objects) {
i(args...);
}
}
template< class T, class... Args >
void update_callable( const std::vector<T>& objects, Args&&... args ) {
using callable = callable_with_args<
T const&, typename std::add_lvalue_reference<Args>::type...
>;
update_callable_args(callable(), objects, std::forward<Args>(args)...);
}
Note that I coerce the argument types to lvalue references, to avoid having any of the callables "eat" rvalue reference arguments causing later callables to see moved-from objects. If you want the rvalues to possibly be moved from, remove the coercion in update_callable
:
using callable = callable_with_args<
T const&, Args&&...
>;
and forward them to each callable in update_callable_args
:
for (auto&& i : objects) {
i(std::forward<Args>(args)...);
}
VS2013 at least seems to compile it properly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With