6

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):

  1. if T is callable with Args arguments, then call all T objects with the arguments.
  2. if T is not callable with Args arguments, then do exactly nothing.

B. That would be ok for me, but ideally I would like update_callabe() overloads that follow these rules (in priority order):

  1. if T is callable with Args arguments, then call all T objects with the arguments.
  2. if T is callable with NO arguments, then call all T objects with no arguments.
  3. if T is not callable with Args arguments nor NO arguments, then do exactly nothing.

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:

  • it's a simplified example, I didn't try to compile it but it is close to my code;
  • (2) basically we want to call the default update of the objects stored if the type of the objects provide one, "default update" meaning here the call operator either with arguments from the update context or with no arguments if the type don't need them;
  • (3) there is a second update loop for "controller" objects that can manipulate externally the stored objects;
TemplateRex
  • 65,583
  • 16
  • 147
  • 283
Klaim
  • 60,771
  • 31
  • 121
  • 186
  • Does VS2013 have `std::result_of`? http://en.cppreference.com/w/cpp/types/result_of – aschepler Jun 30 '14 at 17:14
  • @aschepler Yes, it also have decltype() which I use usually in combination with is_conditional to solve similar issues, but I couldn't find a way to express what I want with it so far. – Klaim Jun 30 '14 at 17:15
  • what about zero arguments? which version should have priority? – TemplateRex Jun 30 '14 at 17:42
  • @Klaim I may come back to your question, but you could write a small function which, when called, constructs overload of the function using templates and RTTI ( basically depending on your compiler implementation, creating a function either at compile time with constant expressions, or at runtime) , then you could create a nice clean interface to the overloaded functions, it would take some template tricks but now you would have some overloads, you may have to use some functors. I just wanted to throw that out there, as a said I may come back your question. – The Floating Brain Jun 30 '14 at 17:43
  • @TemplateRex That's part of the issue but the wanted behaviour is described in order of priority, so if there is a call operator and it match the argument list (even whene there is none) it should be called. – Klaim Jun 30 '14 at 17:47
  • @TheFloatingBrain I don't see how RTTI can help here because the whole overload operation should be done at compile time where we know the type. But maybe I'm missing some weird tricks. – Klaim Jun 30 '14 at 17:49
  • @Klaim With the solution I proposed, when calling the function creating function, depending on your compiler implementation, it would either eval it as a constant expression and create the function at compile or it would create the function at runtime, you might be able to do something using constexpr or somthing in the ISO standard if you really want to be done at compile time, but largely I think it's out of your control, as I believe the definition of such functions ( everytime you call a template function with template arguments it creates a new function) is defined to be implementatio spe – The Floating Brain Jun 30 '14 at 17:57
  • @Klaim also I'm not sure but if you only use constant expressions when filling in a template arguments (and I'm not sure about this but in the function as well possibly) the compiler may be forced by the ISO standard to create the function at compile time. – The Floating Brain Jun 30 '14 at 18:01
  • 6
    [Here's something for you to play around with.](http://coliru.stacked-crooked.com/a/305ad0dae67cf7f9) ([VC++](http://rextester.com/EEN96321)) – Xeo Jun 30 '14 at 18:07
  • @TemplateRex: [I don't think that's anything special anymore.](http://stackoverflow.com/a/9154394/500104) :) – Xeo Jun 30 '14 at 18:12
  • @TemplateRex comma operator is old indeed. What's awesome there is `priority` :) – jrok Jun 30 '14 at 18:20
  • @jrok priority is nice here, just like the iterator tags to use derived-to-base conversion as tie-breaker in overload resolution (in case there would be multiple function call operators inside `F`) – TemplateRex Jun 30 '14 at 18:21
  • @Xeo it's close to what I initially tried but more complete and seem to work with not-callable too! I'll have to check if VS2013 can work with that but otherwise you whould post an answer based on that? – Klaim Jun 30 '14 at 18:27

3 Answers3

3

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.

aschepler
  • 65,919
  • 8
  • 93
  • 144
  • Higher priority == earlier, more important. The reverse ordering doesn't make much sense with that naming. ;) – Xeo Jun 30 '14 at 22:58
  • @Xeo: I'll be sure to mention that the next time my boss says "This is your #1 priority." – aschepler Jun 30 '14 at 23:28
  • Oh, okay, I was thinking priority-levels. :) – Xeo Jul 01 '14 at 07:28
  • I do wonder which usage would feel more natural to more programmers though. – aschepler Jul 01 '14 at 12:54
  • I'm trying this solution, it makes sense but I'm having trouble making it work so far. 1) I am I correct that it should be `template using priority = int priority_helper::*;` where I had to add ``? 2) I only half understand the nullptr: you use normal function overloading rules to get through the candidates, am I correct? 3) I am getting ambiguous calls for now, VS2013 saying all 3 overload are correct. Maybe I should try with enable_if instead? 4) I had to remove the consts on T because it's about "updating" the object, it have to be writeable. – Klaim Jul 03 '14 at 15:05
  • Correct about the `= int priority_helper::*;`. `nullptr` wasn't correct there, which caused the ambiguous errors. I've edited my answer to fix those things (and also use `,void()` instead of `always_void`). I took the `const` from your `const vector&`, which won't allow you to modify the objects. But yes, you can also do it with fewer `const`s. A working demo: http://coliru.stacked-crooked.com/a/a386994771f29b65 – aschepler Jul 04 '14 at 00:33
1

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.

Casey
  • 38,510
  • 6
  • 83
  • 116
0

Here is a partial solution using expression SFINAE (not sure if your VC version supports that)

template<class T>
auto update_callable(const std::vector<T>&, ...) 
    -> void
{ 
}

template< class T, class... Args>
auto update_callable( const std::vector<T>& objects, Args&&... args) 
    -> decltype(std::declval<T>()(std::forward<Args>(args)...))
{ 
    for (auto&& elem : objects)
        elem(std::forward<Args>(args)...);
}

template< class T, class... Args>
auto update_callable( const std::vector<T>& objects, Args&&... args) 
    -> decltype(std::declval<T>()())
{ 
    for (auto&& elem : objects)
        elem();
}

It's a partial solution because the varargs ... argument of the first overload only supports POD arguments (which is why I pasted in the std::vector<T> argument since a vector isn't POD).

You can test it like this:

struct N {};

struct Z 
{ 
    void operator()() const
    { std::cout << "Z(), "; }
};

struct T
{   
    void operator()(int x, int y) const
    { std::cout << "T(," << x << "," << y << "), "; }
};

int main()
{
    auto vN = std::vector<N>(1); 
    auto vZ = std::vector<Z>(2); 
    auto vT = std::vector<T>(3); 
    
    update_callable(vN, 1, 2);std::cout << "end of 1st\n";
    update_callable(vZ, 1, 2);std::cout << "end of 2nd\n";
    update_callable(vT, 1, 2);std::cout << "end of 3rd\n";
}

Live Example that outputs

end of 1st

Z(), Z(), end of 2nd

T(1,2), T(1,2), T(1,2), end of 3rd

Community
  • 1
  • 1
TemplateRex
  • 65,583
  • 16
  • 147
  • 283
  • It's very close to what I tried initially. Am I correct that it doesn't work with non-default constructible types? – Klaim Jun 30 '14 at 18:30
  • @Klaim that's right, but you could use Xeo's version from the comments, which constructs a priority hierarchy which is much more robust as it doesn't rely on the `...` varargs – TemplateRex Jun 30 '14 at 18:33
  • `update_callable(vZ)` is ambiguous. You also get an ambiguity if your callable type has `operator()` overloaded for both zero args and N args. – aschepler Jun 30 '14 at 19:24
  • @aschepler yeah, it's a partial solution, I like yours better – TemplateRex Jun 30 '14 at 19:26