2

I'm trying to create a variadic function that takes any amount of arguments, but I'd like to specialize the case where only two arguments with iterators are passed in. Passing in two arguments of non-iterators, should still use the generic variadic version. I'm hitting a static_assert failure that I haven't been able to overcome. It seems like it's trying to evaluate the entire expression in with_iterator_args, which fails if the function has less than two arguments, rather than skipping the evaluation of the remainder when the check for 2 arguments already yielded false.

Is there a way to do this without adding two more overloads for the one and two argument case?

This is what I have so far:

#include <iostream>
#include <vector>
#include <tuple>

// inspired by https://stackoverflow.com/a/7943765/2129246
template <typename... Args>
struct args_traits
{
    enum { arity = sizeof...(Args) };

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };
};

// based on: https://stackoverflow.com/a/30766365/2129246
template <typename T>
struct is_iterator
{
    static char test(...);

    template <typename U,
        typename=typename std::iterator_traits<U>::difference_type,
        typename=typename std::iterator_traits<U>::pointer,
        typename=typename std::iterator_traits<U>::reference,
        typename=typename std::iterator_traits<U>::value_type,
        typename=typename std::iterator_traits<U>::iterator_category
    > static long test(U&&);

    constexpr static bool value = std::is_same<decltype(test(std::declval<T>())),long>::value;
};

template<typename Arg1, typename Arg2>
struct is_iterator_args
{
    constexpr static bool value = is_iterator<Arg1>::value && is_iterator<Arg2>::value;
};

template<typename... Args>
struct with_iterator_args
{
    constexpr static bool value = args_traits<Args...>::arity == 2
        && is_iterator_args<typename args_traits<Args...>::template arg<0>::type, typename args_traits<Args...>::template arg<1>::type>::value;
};

template <typename T, typename... Args,
    typename = typename std::enable_if<!with_iterator_args<Args...>::value>::type>
void some_func(T first, Args&&... args)
{
    std::cout << "func(" << first << ") called with " << sizeof...(args) << " args" << std::endl;
}

template <typename T, typename Begin, typename End,
    typename = typename std::enable_if<is_iterator_args<Begin, End>::value>::type>
void some_func(T first, Begin begin, End end)
{
    std::cout << "func(" << first << ") called with iterators: " << std::distance(begin, end) << std::endl;
}

int main()
{
    std::vector<int> v{1, 2, 3};

    some_func(1, v.begin(), v.end()); // special case, using iterators
    some_func(1, "arg2", 3, std::string("arg4"));
    some_func(1, "arg2");
    some_func(1);
    some_func(1, "arg2", 3, std::string("arg4"), 5.67);
    return 0;
}

This is what fails:

In file included from test.cpp:3:
/usr/include/c++/9/tuple: In instantiation of ‘struct std::tuple_element<0, std::tuple<> >’:
/usr/include/c++/9/tuple:1285:12:   required from ‘struct std::tuple_element<1, std::tuple<const char (&)[5]> >’
test.cpp:14:69:   required from ‘struct args_traits<const char (&)[5]>::arg<1>’
test.cpp:45:3:   required from ‘constexpr const bool with_iterator_args<const char (&)[5]>::value’
test.cpp:49:37:   required by substitution of ‘template<class T, class ... Args, class> void some_func(T, Args&& ...) [with T = int; Args = {const char (&)[5]}; <template-parameter-1-3> = <missing>]’
test.cpp:68:21:   required from here
/usr/include/c++/9/tuple:1303:25: error: static assertion failed: tuple index is in range
 1303 |       static_assert(__i < tuple_size<tuple<>>::value,
      |                     ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Tom
  • 653
  • 1
  • 8
  • 15

2 Answers2

0

I seem to be able to only make it work by adding more overloads, and not using with_iterator_args:

template <typename T, typename... Args>
void some_func_common(T first, Args&&... args)
{
    std::cout << "func(" << first << ") called with " << sizeof...(args) << " args" << std::endl;
}

template <typename T, typename A>
void some_func(T first, A arg)
{
    some_func_common(first, arg);
}

template <typename T>
void some_func(T first)
{
    some_func_common(first);
}

template <typename T, typename A1, typename A2, typename... Args,
    typename = typename std::enable_if<!is_iterator_args<A1, A2>::value>::type>
void some_func(T first, A1 begin, A2 end, Args&&... args)
{
    some_func_common(first, std::forward<A1>(begin), std::forward<A2>(end), std::forward<Args>(args)...);
}

template <typename T, typename Begin, typename End,
    typename = typename std::enable_if<is_iterator_args<Begin, End>::value>::type>
void some_func(T first, Begin begin, End end)
{
    std::cout << "func(" << first << ") called iterators: " << std::distance(begin, end) << std::endl;
}

It seems unnecessarily messy, though.

user2129246
  • 109
  • 1
  • 5
0

The problem in your case is that both expression before and after && must compile - even if the expression after the && will not be used.

My first try was to leverage C++17 constexpr if.

template<typename... Args>
struct with_iterator_args
{
private:
    constexpr static bool value_checker() {
        if constexpr (args_traits<Args...>::arity == 2) {
            return is_iterator_args<typename args_traits<Args...>::template arg<0>::type, typename args_traits<Args...>::template arg<1>::type>::value;
        }
        else {
            return false;
        }
    }

public:
    constexpr static bool value = value_checker();
};

If you need to stick with C++11, you could use std::conditional. Please note that I also use std::false_type and std::true_type.

template<typename... Args>
struct is_iterator_args :
    std::conditional<is_iterator<typename args_traits<Args...>::template arg<0>::type>::value &&
                     is_iterator<typename args_traits<Args...>::template arg<1>::type>::value,
                    std::true_type, std::false_type>::type
{
};

template<typename... Args>
struct with_iterator_args :
     std::conditional<sizeof...(Args) == 2, is_iterator_args<Args...>, std::false_type>::type
{
};
Werner Henze
  • 16,404
  • 12
  • 44
  • 69
  • Awesome, unfortunately `if constexpr` seems to be a C++17 feature. Seems like overloads are really the only workable solution with C++11 only – user2129246 Feb 09 '20 at 20:57
  • @user2129246 Thanks for the hint. I only read the question and the tags and just missed that the title requests a C++11 solution. I adjusted my answer to give a C++17 and a C++11 solution. – Werner Henze Feb 09 '20 at 21:47