C++11 Implicit conversion from initialization list to array parameter
Asked Answered
Y

4

6

In C++11, is it possible to do something similar to the following?

template<typename T, size_t N>
void foo(array<T, N> src) { ... }

...

foo({1, 2, 3})

I'm currently running GCC 4.8.

Yocum answered 30/6, 2013 at 7:52 Comment(0)
C
7

Yes, I managed to get the following work (since you allow something similar):

template<typename T, size_t N>
void foo(array<T, N> src) { ... }

...

foo('a', 'b');
foo(1, 2, 3);

Here is how:

#include <array>
#include <iostream>
#include <utility>
using namespace std;

template<typename T, unsigned long N>
void foo(array<T,N> src) { 

  for (auto e : src)
    cout << e << endl;
}

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = {{ head, tail ... }};
     return a;
}

template<class T, class... Tail> 
void foo(T&& head, Tail&&... values) {

    foo(make_array(std::forward<T>(head), std::forward<Tail>(values)...));
}

int main() {

  foo('a', 'b');

  foo(1, 2, 3);
}

I have tested this with gcc 4.7.2 and with clang 3.4 (trunk 184647), they work as expected.
Here is an online version at Stacked-Crooked. However, this code fails to compile at Ideone. Since I was unable to figure out the options passed to the compiler at Ideone, I've given up on that site.


I have shamelessly stolen the make_array function from @Pavel Minaev's answer to the How to emulate C array initialization “int arr[] = { e1, e2, e3, … }” behaviour with std::array? question. The other make_array suggestions caused compile errors that I couldn't fix.

This make_array function has limitations, please read the entire post; in particular the discussion std::array - if only it knew its size on comp.lang.c++.moderated is referenced. Apparently, getting a reasonable make_array is quite tricky. I wouldn't recommend the simple-minded make_array in this answer to be used in production code.


You wouldn't have any problems if the size was a template argument to std::initializer_list. Hence the question Why is the size not a template argument of std::initializer_list?

Constant answered 30/6, 2013 at 10:54 Comment(2)
Impressive... I think I need to read up some more to understand that make_array... :)Brachiate
Why the downvote? What is wrong with the answer? The OP allows for something similar.Constant
B
3

Apparently not. The standard (14.8.2.5) calls this an non-deduced context;

In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified.

...

The non-deduced contexts are:

...

  • A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type.

Example:

template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T

EDIT: You can make the same thing work with std::vector, if you just use an initializer_list overload to make the deduction of the type work;

template<typename T>
  void foo(const std::vector<T>& src) { ...your code here... }
template<typename T>
  void foo(const std::initializer_list<T>& src) { foo(std::vector<T>(src)); }

foo({1,2,3});  // Compiles

...but sadly, since the size of initializer_list is not a template argument, I can't think of a way to make it deduce and forward the array size from the initializer_list in the same way as the type.

Brachiate answered 30/6, 2013 at 8:10 Comment(2)
The vector solution seems to work without an initializer_list overload.Yocum
@Yocum On which gcc? I only have 4.7.3 to test on currently. EDIT: Ah, 4.8, didn't see that you were the one asking the question :)Brachiate
C
2

You could use an initializer list directly to achieve that syntax. e.g.:

#include <iostream>
#include <initializer_list>

void foo(std::initializer_list<int> il) {
  for (auto i: il)
    std::cout << i < std::endl;
}

int main() {
  foo({1,2,3});
}

or make it more generic:

template <typename T>
void foo(std::initializer_list<T> il) {
  ...
Curitiba answered 30/6, 2013 at 8:54 Comment(1)
Going even further, why not template foo to any container?Boyles
Z
1

It is possible with references to raw arrays:

template <typename T, size_t N>
void foo(T const (&x)[N]) {
    // x is [1, 2, 3], N = 3
}

int main() {
    foo({1, 2, 3});
    return 0;
}

Note that the array must be declared const.

Zibeline answered 27/11, 2020 at 10:59 Comment(5)
This is nice. Do you have any insight why const allows this implicit conversion, and if so, please share it?Endamoeba
Is it because the reference must be const?Endamoeba
I don't know. I just read it in the "C++ Templates" book.Zibeline
I believe I have the same book. Tons of stuff about array and pointer decay. Do you remember the section?Endamoeba
Sections 5.4 and 7.4 from the 2nd edition, I thinkZibeline

© 2022 - 2024 — McMap. All rights reserved.