4

I am trying to create a function that would accept several arguments of a given type, but both type and number of arguments should be specified via templates.

I found that using C++11's initializer_list is probably a good technique in this case, but is it possible to check its size at compile time? Are there any other tecnhiques that can solve this problem?

#include <initializer_list>

// Here I want to define type and number of components for each point

template <typename T, int DIM>
class Geometry
{
public:
    void addPoint(std::initializer_list<T> coords)
    {
        assert(coords.size() == DIM); // Working good, but not compile-time

        // Next line does not compile because size() is not known at compile-time
        static_assert(coords.size() == DIM, "Wrong number of components"); 
    }
};
Dmitry Shurov
  • 434
  • 6
  • 16
  • Somehow this question keeps surfacing. It was gnawed to the bone here. – SergeyA Dec 24 '15 at 20:46
  • 2
    If you want to call `addPoint` using the initializer list syntax, a solution is to use `template void addPoint(const T (& coords)[N])`. You can then `static_assert(N == DIM, "")` in the function body. However, this was relatively recently specified in the Standard draft (a year ago), so Clang doesn't support it yet (looks like it will in 3.8.0). It works in GCC (reasonably recent versions) and Visual C++ 2015. – bogdan Dec 24 '15 at 21:34
  • @bogdan: Nice solution! Though, as far as I see, it has a drawback: when I try to pass an integer instead of the base type _double_ like `geo.addPoint({1.0, 2});` I get the following error: `mismatched types ‘double’ and ‘int’`. Variadic templates allowed me not only to check strict type equality, but to perform type conversion as well. – Dmitry Shurov Dec 24 '15 at 22:16
  • Looks like a bug in GCC - it tries to deduce the element type when it shouldn't. – bogdan Dec 24 '15 at 23:14

3 Answers3

6

You cannot statically assert on a runtime quantity. And the number of values in an initializer_list is decided at runtime, by the caller of the function.

Not even making your function constexpr would work, since evaluation of the function is not required to take place at compile-time.

You should instead use a variadic template.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for clarifying! Never dealed with variadic templates before, but your anwser motivated me to explore this topic. Posted my solution below. Not sure if it is quite elegant, but it solves the problem. – Dmitry Shurov Dec 24 '15 at 21:57
3

Thanks to Nicol, I looked towards the variadic templates. The problem was not only to check the number of arguments, but also to check that their types are convertible to the base type. Here's my solution based on this and this topics. It works as expected in GCC 4.9.

template<class T, class...>
struct are_convertible : std::true_type
{};

template<class T, class U, class... TT>
struct are_convertible<T, U, TT...>
    : std::integral_constant<bool, std::is_convertible<T,U>{} && are_convertible<T, TT...>{}>
{};

template <typename T, int DIM>
class Geometry
{
public:
    template<typename... Args>
    void addPoint(Args... coords)
    {
        static_assert(sizeof...(coords) == DIM, "Number of components does not match template");
        static_assert(are_convertible<T, Args...>{}, "All arguments' types must be convertible to the template type"); 
    }
};
Community
  • 1
  • 1
Dmitry Shurov
  • 434
  • 6
  • 16
  • 2
    `std::is_convertible` checks that `T` is convertible to `U`, not the other way around. Also keep in mind that initializer lists prohibit narrowing conversions, while this solution doesn't. – bogdan Dec 24 '15 at 23:20
2

Adding a POD class Point with DIM data members wout suit your purpose best. Then the implicit constructor call would make sure that everything was OK.

TheROE
  • 134
  • 3