38

I have the following problem:

template< size_t... N_i >
class A
{
  public:

    // ...

    void foo()
    {
      bar( /* 0,...,0 <- sizeof...(N_i) many */);
    }
};

I want to call a function bar and pass sizeof...(N_i) many arguments to it which are all zeros, e.g., bar(0,0,0) in case sizeof...(N_i) == 3. How can this be implemented?

abraham_hilbert
  • 2,221
  • 1
  • 13
  • 30

4 Answers4

60
bar(((void)N_i, 0)...);

The comma operator will discard N_i, yielding just the right-hand operand's value (0). The cast is to prevent a warning about N_i being discarded.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 6
    You gotta respect C++ masters... Hats off – WhiZTiM Oct 24 '16 at 12:31
  • 2
    @WhiZTiM I may be a master, but not for the above! – Columbo Oct 24 '16 at 12:32
  • 14
    @SamVarshavchik That works for 0, but not for any other value. Answers should be as general as possible. – Columbo Oct 24 '16 at 12:33
  • 2
    @SamVarshavchik that could've taken a different overload since it'd be a bunch of `size_t` zeros – krzaq Oct 24 '16 at 12:33
  • @Columbo, Well, It takes Mastery to do things in **really simple** ways... I was almost off writing the *usual* template hackery – WhiZTiM Oct 24 '16 at 12:33
  • 1
    It'll work for any value. Multiply by 0, then add the value of your choice. More than one way to skin a cat. – Sam Varshavchik Oct 24 '16 at 12:34
  • 3
    @SamVarshavchik And you really think that `N_i*0 + 15` is clearer than `(N_i, 15)`? Unless the reader doesn't understand the comma operator, which is a long shot, I shouldn't think so. Also note krzaq's excellent point, which is that due to the usual arithmetic conversions, the type of your expansion pattern could be `size_t` and not `int`. – Columbo Oct 24 '16 at 12:38
  • @NathanOliver Fair enough. – Columbo Oct 24 '16 at 12:40
  • 4
    Good answer, though if you wanted it as 'generic' as possible, then it should be foo(f(N_i)...) which will expand to call f() for each argument... which will allow you to edit each argument to be any value. You've just specialised f() in this case to be a fixed value. – UKMonkey Oct 24 '16 at 13:22
  • @Columbo this smells like something that should be a use case for `std::integer_sequence` – Mgetz Oct 24 '16 at 14:41
  • @Mgetz : `std::integer_sequence` is to _generate_ an interesting `IntegralT...`; here we already have a `size_t...`, so `std::integer_sequence` has nothing to contribute. – ildjarn Oct 25 '16 at 01:44
  • @ildjarn `std::integer_sequence` generates values we can multiply by zero without having a recursive implementation, most implementations are `log n` and will compile much faster than what is above – Mgetz Oct 25 '16 at 11:26
  • @Mgetz why would it compile faster? – Columbo Oct 25 '16 at 11:33
  • @Columbo because maintainers like STL made the compilers implement the magic of the sequence because they didn't want to deal with it. – Mgetz Oct 25 '16 at 11:38
  • 2
    @Mgetz I don't understand what you're referring to. In the above code, we already have a pack, and there's no reason to introduce another level of indirection - it will definitely be slower. – Columbo Oct 25 '16 at 12:50
  • @Mgetz as ildjarn already pointed the sequence of `size_t`s is already given we don't have to generate it here.. OP wanted to know how to change this sequence to the sequence of zeros of the same length... – W.F. Oct 25 '16 at 12:50
9

Despite the undoubtedly interesting answer by @Columbo, I want to suggest another viable solution based on constexpr'd template variables:

 #include <cstddef> 

 template<std::size_t, std::size_t V>
 constexpr std::size_t repeat_value = V;

 template<std::size_t... N_i>
 class A {
     template<typename... Args>
     void bar(Args&&...) { }

 public:   
      void foo() {
           // repeat N_i times value 0
           bar(repeat_value<N_i, 0>...);
      }
 };

 int main() {
      A<0, 1, 2, 3, 4> a;
      a.foo();
 }

I find it easier to read at least, even if it's bad in terms of performance at compile-time.


You can easily generalize it as it follows:

template<std::size_t, typename T, T V>
constexpr T repeat_value = V;

The invokation in the specific case is this:

bar(repeat_value<N_i, int, 0>...);
Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Naming it `repeat_value` when it doesn't repeat anything doesn't seem like a good idea, but then again, `std::move` doesn't move anything either. –  Oct 25 '16 at 09:03
  • 3
    @hvd Ahahah... So, I'm compliant with the standard. :-) – skypjack Oct 25 '16 at 09:05
2

You could also use templates to simulate something similar. This is a very basic solution and will just create a list of 0s, but it could be extended to generate other sequences if that was desired.

template <size_t Unused>
struct Wrap {
  static constexpr size_t value = 0;
};

template <size_t... N_i>
class A {
public:
    void foo() {
        bar(Wrap<N_i>::value...);
    }
};

Which will just expand into a list of zeros of the same size as the N_i arguments. Admittedly, the interface is slightly different.

For a complete example, which shows the values of the elements which bar receives, see here: Live example

RobClucas
  • 815
  • 7
  • 16
0

You can also use the fact that if we subtract any positive integer from itself we get 0.

template< size_t... N_i >
class A
{
  public:

    void foo()
    {
    //------vvvvvvvvv-------->expand to N_i 0's
      bar( (N_i - N_i)...);
    }
    //for ending recursion 
    void bar()
    {

    }
    //generalized version
    template<typename T, typename... Types>
    void bar(T t, Types... types)
    {
            std::cout << t << "\n";
            bar(types...);
    }
};
int main()
{
    A<1,2,3,4,10> a;
    a.foo();
}
Jason
  • 36,170
  • 5
  • 26
  • 60