18

I have a class with a template that accepts an integer:

template <unsigned int N>
class Example {};

I'm looking for a way to define a (member)function that accepts some amount of Example objects as arguments. The amount is to be determined by N, so the function would be used like this:

Function(Example<2>(), Example<2>());
Function(Example<3>(), Example<3>(), Example<3>());

What I tried so far:

Using an initializer list, one is able to pass a set of objects to the function:

template <unsigned int N>
void Function(std::initializer_list<Example<N>> list);
//...
Function({Example<2>(), Example<2>()});

However, the problem besides the fact that really only one argument is passed(the list), is that with this method any number of arguments can be used:

Function({Example<2>()});

I also tried using a variadic function:

template <unsigned int N>
void Function(Example<N> e...)
{
    va_list args;
    va_start(args, e);
    //...
}
Function(Example<2>(), Example<2>());

This makes it possible to use real parameters, but the problem of using any number of arguments remains, and it's not possible to know how many arguments were actually passed.

Jannis
  • 233
  • 1
  • 7
  • of possible interest, complementary to Vittorio Romeo's answer: https://stackoverflow.com/questions/39659127/restrict-variadic-template-arguments – bolov Dec 18 '17 at 10:46

5 Answers5

21

Assuming you want the number of arguments to be deduced from the Example<N> type, and that all Example<I> should share the same such N, a C++17 solution might be

template <unsigned int... I>
auto Function( Example<I>... ) ->
    std::enable_if_t<( ( I == sizeof...(I) ) && ... )>
{
   // or static_assert() if you always want an error
}
Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
  • Could you please check my answer? I think it can be done in C++11/C++14. – Sergey.quixoticaxis.Ivanov Dec 18 '17 at 17:10
  • 1
    @Sergey.quixoticaxis.Ivanov sure it can, but a simple, recursively defined type trait would do in that case; the specialization being like *"struct AreSameExamples: std::conditional< I == N, AreSameExamples, std::false_type >::type {};"*... – Massimiliano Janes Dec 18 '17 at 17:22
  • Sorry, I'm new to writing templated (or at least heavily templated) code. Do you mean it's easier to check that all arguments are the same specialization of `S` with a template you described? – Sergey.quixoticaxis.Ivanov Dec 18 '17 at 17:30
5

Make Function a variadic template and use std::enable_if_t to constrain your it:

  • Some IsExample trait can be used to make sure that all arguments are instances of Example

  • sizeof...(pack) can be used to get the size of the parameter pack

template <unsigned int N, typename... Ts>
auto Function(Ts... xs) 
    -> std::enable_if_t<(IsExample<Ts>::value && ...) 
                     && (sizeof...(Ts) == N)>
{
}

live example on wandbox

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
4

You should utilize variadic function template with static_assert. Unlike approaches involving enable_if this one will produce a readable error message if incorrect arguments are passed.

template<unsigned int ... I>
void Function(Example<I>... items)
{
    static_assert
    (
        true && (... && (static_cast<unsigned int>(sizeof...(I)) == I))
    ,   "This function accepts N arguments of type Example<N>"
    );
}

Online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84
2

There are many answers that cover SFINAE friendly based constraints, but I don't like placing my SFINAE in the return value:

template <unsigned int... Is,
  std::enable_if_t<( ( Is == sizeof...(Is) ) && ... ), bool> = true
>
void Function( Example<Is>... examples )
{
  // code
}

or

template<bool b>
using test_requirement = std::enable_if_t<b, bool>;

template <unsigned int... Is,
  test_requirement<( ( Is == sizeof...(Is) ) && ... )> = true
>
void Function( Example<Is>... examples )
{
  // code
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

+1 for the Massimiliano Janes's elegant solution.

Unfortunately use folding so works only for C++17.

To test, with C++11/C++14, that all I are equals to sizeof...(I) (and maybe that sizeof...(I) is equal to N, where N is the class template argument), it's enough test that a variadic type, that receive unsigned values, is the same type with a different order of values.

I mean: declaring a trivial struct as

template <std::size_t ... Is>
struct IList;

the test can be

std::is_same<IList<N, sizeof...(Is), Is...>,
             IList<sizeof...(Is), Is..., N>>::value

Starting from C++14 it's possible to use std::index_sequence instead of IList.

So Example can be written as

template <unsigned int N>
struct Example
 {
   template <unsigned int ... Is>
   auto Function (Example<Is> ...)
      -> typename std::enable_if<
            std::is_same<IList<N, sizeof...(Is), Is...>,
                         IList<sizeof...(Is), Is..., N>>::value>::type
    { /* do something */ }
 };

The following is a example of use (but remember to include <type_traits>)

int main()
 {
   Example<1U> e1;
   Example<2U> e2;

   // e1.Function(); // error
   e1.Function(Example<1>{}); // compile
   //e1.Function(Example<1>{}, Example<1>{}); // error

   // e2.Function(); // error
   //e2.Function(Example<2>{}); // error
   e2.Function(Example<2>{}, Example<2>{}); // compile
   //e2.Function(Example<2>{}, Example<2>{}, Example<2>{}); // error
 }
max66
  • 65,235
  • 10
  • 71
  • 111