Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ variadic template with doubles

The following code

#include <initializer_list>
#include <vector>

template<int ...>
const std::vector<int>*make_from_ints(int args...)
{ return new std::vector<int>(std::initializer_list<int>{args}); }

is compiling (with GCC 6.3, on Debian/Sid/x86-64) correctly, and I expect it for a call like

auto vec = make_from_ints(1,2,3);

to return a pointer to some vector of integers containing 1, 2, 3.

However, if I replace int by double, that is if I add the following (in the same basiletemplates.cc file ...) code:

template<double ...>
const std::vector<double>*make_from_doubles(double args...)
{ return new std::vector<double>(std::initializer_list<double>{args}); }

I'm getting a compile error:

basiletemplates.cc:8:17: error: ‘double’ is not a valid type
                for a template non-type parameter
 template<double ...>
                 ^~~

and I don't understand why. After all both int and double are scalar numerical POD types (predefined in the C++11 standard).

How to get a template variadic function to be able to code:

auto dvec = make_from_doubles(-1.0, 2.0, 4.0);

and get a pointer to some vector of doubles containing -1.0, 2.0, 4.0 ?

BTW, compiling for C++14 (with g++ -Wall -std=c++14 -c basiletemplates.cc), and using clang++ (version 3.8.1) instead of g++ dont change anything.

like image 598
Basile Starynkevitch Avatar asked Apr 03 '17 09:04

Basile Starynkevitch


1 Answers

template<int ...>
const std::vector<int>*make_from_ints(int args...)
{ return new std::vector<int>(std::initializer_list<int>{args}); }

The snippet above has a multitude of issues:

  • Returning a const std::vector<int>* instead of a std::vector<int> and unnecessarily using dynamic allocation.

    • Even if you wanted to use dynamic allocation, you should use std::make_unique instead of new.
  • You defined make_from_ints to be template function that takes any amount of int template parameters, but you're not giving those ints a name - you cannot ever use them!

  • Your signature is actually being parsed as make_from_ints(int args, ...) - this is a C va_args signature that has nothing to do with variadic templates.

    • The correct syntax for an argument pack is type... name.

If you want to accept any number of arguments of a specific type that works nicely with template argument deduction, the easiest way is to use a regular variadic template that accepts an arbitrary amount of types and static_asserts their type (or uses std::enable_if for SFINAE-friendliness). Here's an example:

template <typename... Ts>
auto make_from_ints(Ts... xs) 
{ 
    static_assert((std::is_same<Ts, int>::value && ...));
    return std::vector<int>{xs...};
}

template <typename... Ts>
auto make_from_doubles(Ts... xs) 
{ 
    static_assert((std::is_same<Ts, double>::value && ...));
    return std::vector<double>{xs...};
}

Usage:

for(auto x : make_from_ints(1,2,3,4)) std::cout << x << " ";
std::cout << "\n";
for(auto x : make_from_doubles(1.0,1.5,2.0,2.5)) std::cout << x << " ";

1 2 3 4

1 1.5 2 2.5

live example on wandbox


Note that I'm using a C++17 fold expression to check if all Ts... are of a particular type here:

static_assert((std::is_same<Ts, int>::value && ...));

If you do not have access to C++17 features, this can be easily replaced with something like:

template <typename... Ts>
constexpr auto all_true(Ts... xs)
{
    for(auto x : std::initializer_list<bool>{xs...}) 
        if(!x) return false;

    return true;
}

// ...

static_assert(all_true(std::is_same<Ts, int>{}...));
like image 94
Vittorio Romeo Avatar answered Oct 30 '22 14:10

Vittorio Romeo