3

I am trying to implement a constexpr function "add42" that would allow me to do:

constexpr array<int,5> arr = {1,2,3,4,5};
constexpr array<int,5> arr2 = add42(0,arr); //I want arr2=={43,2,3,4,5}

That is, add an integer at a given index to an array statically (with constexpr). Since my array "arr" is immutable, I have to actually create a new one from "arr" and my index. That is why I coded this function:

template<int DIM, typename... ARGS> auto constexpr
add42(int index, array<int,DIM> intergerArray, ARGS... unpackedIntegers) -> array<int,DIM> {
    return
        ( sizeof...(ARGS)==DIM ) ?
            array<int,DIM>( {{unpackedIntegers...}} ) :
        ( (sizeof...(ARGS)-1)==index ) ?
            add42(index, intergerArray, unpackedIntegers..., intergerArray[sizeof...(ARGS)-1]+42 ) :
            add42(index, intergerArray, unpackedIntegers..., intergerArray[sizeof...(ARGS)-1] ) ;
}

That is, all the integers of my array are recursively unpacked from the array, added 42 if at the right index, and appened at the end of the ARGS list. When this arg list contains all the integers from the array, we are done so we can repack into a new array.

However I get this error (gcc 4.7.2)

error: no matching function for call to 'add42(int, const std::array<int, 5u>&)'|
note: candidate is:|
template<int DIM, class ... ARGS> constexpr std::array<int, DIM> add42(int, std::array<int, DIM>, ARGS ...)|
note:   template argument deduction/substitution failed:|
note:   mismatched types 'int' and '#'integer_cst' not supported by dump_type#<type error>'|

Can you explain me what is the problem and how to correct it ?

This question seems similar to C++11: Compile Time Calculation of Array but is not (At least, I am unable to figure out how to use it deirectly): here, I want to create an new array from an already existent one, not from a known sequence of integers.

EDIT

Now I get infinite instantiation, even when no recursion called. Here is a simplified example :

template<size_t DIM, typename... ARGS> auto constexpr
add42(int index, array<int,DIM> integerArray, ARGS... unpackedIntegers) -> array<int,DIM> {
    return
        ( true ) ?
            array<int,DIM>( {{unpackedIntegers...}} ) :
            add42(index, integerArray, unpackedIntegers..., integerArray[(sizeof...(ARGS)-1)] ) ;
}

Why does my compiler try to compile the last function call ?

EDIT 2

Apparently, I have to provide 2 function in order not to confuse the compiler:

template<size_t DIM, class... ARGS> constexpr auto
add42(int index, array<int,DIM> integerArray, ARGS... unpackedIntegers) -> typename enable_if<sizeof...(ARGS)==DIM ,array<int,DIM>>::type
{
    return array<int,DIM>( {{unpackedIntegers...}} );
}
template<size_t DIM, class... ARGS> constexpr auto
add42(int index, array<int,DIM> integerArray, ARGS... unpackedIntegers) -> typename enable_if<sizeof...(ARGS)!=DIM ,array<int,DIM>>::type
{
    return
        ( sizeof...(ARGS) == index ) ?
            add42(index, integerArray, unpackedIntegers..., integerArray[sizeof...(ARGS)]+42) :
            add42(index, integerArray, unpackedIntegers..., integerArray[sizeof...(ARGS)]) ;
}

But it still does not work:

recursively required from [[name of the second function]]

Apparently, a variadic function cannot call "recursively" one of its overloads. Am I right ? What workaround is possible ?

Community
  • 1
  • 1
Bérenger
  • 2,678
  • 2
  • 21
  • 42
  • `sizeof` returns a `size_t`, which is an **unsigned** integer type. You probably don't want to subtract `1` from an unsigned integer that could be `0` (first recursion, `sizeof...(ARGS)` is `0`). – dyp May 23 '13 at 16:17
  • @DyP My mistake. I edited to remove these -1. But still does not work :( – Bérenger May 23 '13 at 16:29
  • With the constexpr operator[] and clang 3.2 it works, though you'd probably better use only one pair of braces in the first add42 overload -- or just use return `{unpackedIntegers...};`. AFAIK the second pair invokes the copy-ctor. (One pair for ctor, one for member array initialization.) – dyp May 23 '13 at 17:34

2 Answers2

4

You should use

template<size_t DIM, typename... ARGS> auto constexpr
add42(int index, array<int,DIM> intergerArray, ARGS... unpackedIntegers)

since array's second parameter has type size_t, not int.

ForEveR
  • 55,233
  • 2
  • 119
  • 133
3

Unfortunately, std::array::operator[] is not constexpr.

Compiler options: just plain -std=c++11

gcc <= 4.6: -std=c++0x and replace the using directive with a typedef

#include <cstddef>
//#include <array>
#include <type_traits>
#include <iostream>

template < typename T, std::size_t dim >
struct c_array
{
    T arr[dim];

    constexpr T operator[](std::size_t index)
    {  return arr[index];  }

    T const* begin() const
    {  return arr;  }
    T const* end() const
    {  return arr+dim;  }
};


// I like the overloaded version better (instead of enable_if) :)

template < typename T, std::size_t dim, typename... TT >
constexpr c_array<T, dim>
add_to(T s, c_array<T, dim> in, std::size_t index, std::true_type, TT... pp)
{
    return {{pp...}};
}

template < typename T, std::size_t dim, typename... TT >
constexpr c_array<T, dim>
add_to(T s, c_array<T, dim> in, std::size_t index, std::false_type, TT... pp)
{
    using test = std::integral_constant<bool, (sizeof...(pp)+1 == dim)>;

    return   index == sizeof...(pp)
           ? add_to(s, in, index, test{}, pp..., in[sizeof...(pp)]+s)
           : add_to(s, in, index, test{}, pp..., in[sizeof...(pp)]  );
}


// unfortunately, I don't know how to avoid this additional overload :(

template < typename T, std::size_t dim>
constexpr c_array<T, dim>
add_to(T s, c_array<T, dim> in, std::size_t index)
{
    return add_to(s, in, index, std::false_type{});
}


constexpr c_array<int,5> arr = {1,2,3,4,5};
constexpr c_array<int,5> arr2 = add_to(42, arr, 0); //I want arr2=={43,2,3,4,5}

int main()
{
    for(auto const& e : arr2)
    {
        std::cout << e << ", ";
    }
}

Alternative version, slightly awkward usage syntax:

// helper; construct a sequence of non-type template arguments
template < std::size_t... tt_i >
struct seq
{};

template < std::size_t t_n, std::size_t... tt_i >
struct gen_seq
    : gen_seq < t_n-1, t_n-1, tt_i...>
{};

    template < std::size_t... tt_i >
    struct gen_seq < 0, tt_i... >
        : seq < tt_i... >
    {};

template < std::size_t index, typename T, std::size_t dim,
           std::size_t... tt_bef, std::size_t... tt_aft >
constexpr c_array<T, dim>
add_to(T s, c_array<T, dim> in, seq<tt_bef...>, seq<tt_aft...>)
{
    return {{ in[tt_bef]..., in[index]+s, in[tt_aft]... }};
}

template < std::size_t index, typename T, std::size_t dim >
constexpr c_array<T, dim>
add_to(T s, c_array<T, dim> in)
{
    return add_to<index>(s, in, gen_seq<index>{}, gen_seq<dim-index-1>{});
}


constexpr c_array<int,5> arr = {1,2,3,4,5};
constexpr c_array<int,5> arr2 = add_to<0>(42, arr);
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Ok Thank you. I don't mind using another container than std::array, I think this is a small oversight of the ISO standard. On GCC 4.7.2, it does not work though. I have the same error. Can you just edit your answer with the particular version of clang you use, and with what options (-std=c++14 e.g.) I will mark it as resolved. – Bérenger May 23 '13 at 17:38
  • @BérengerBerthoul Hm the second pair of braces seems to be required for gcc 4.7. Compiler options just `-std=c++11`. Try again with edited code. – dyp May 23 '13 at 17:42
  • It does not work with mingw32-g++.exe -Wall -g -O3 -Wall -std=gnu++11 -Wextra -Wall (nor without -O3). But as you can see I run it on windows so that may be the difference – Bérenger May 23 '13 at 17:46
  • @BérengerBerthoul All I get on my g++ 4.7.2 mingw are warnings using those options.. but no errors – dyp May 23 '13 at 17:50
  • Just one side question: does this code make sense ? I mean, modifying just a little and we are able to add two arrays at compile time, which should be quite common. Nevertheless, there is no sign of this kind of method being used anywhere... – Bérenger May 23 '13 at 17:51
  • @BérengerBerthoul I'd say you'd normally try to reuse code e.g. by using the `gen_seq` -> `seq` approach. It isn't shorter here but if you have it anyway you can write this much easier I guess (could post a second answer using that..) – dyp May 23 '13 at 17:54
  • @DyP You, sir, are a new kind of evil genius. Thanks a lot for this technique. I've expanded on it to do compile-time string concatenation here: https://gist.github.com/JonValdes/7124736 – hasvn Oct 23 '13 at 19:16
  • @hasvn Oh, err, thank you. Compile-time string concatenation? Reminds me of [this](http://stackoverflow.com/q/13292237/420683), [this](http://stackoverflow.com/q/15858141/420683) and [this blog](http://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/).. ;) – dyp Oct 23 '13 at 19:34
  • @hasvn Note you can take the array by reference and deduce its length; e.g. `template c_array deduce_array_type(T (&param)[N]); auto x = deduce_array_type("hello world");` – dyp Oct 23 '13 at 19:37
  • True! Updated gist after removing c_strlen. Thanks again! As for the other posts and the blog post, I had read most of them already, but I couldn't use those solutions. The std::array-based ones didn't really work in compile-time when inspecting the asm output. Others didn't allow for several consecutive concatenations. Others required boost::mpl, which I'd like to avoid... So I ended up with this in the end. – hasvn Oct 23 '13 at 22:22