Search code examples
c++templatestypesvariadic-templatesvariadic

Accepting N arguments converting into the same type


I'm currently trying to make a vector class in C++. The class should have a constructor which accepts as many arguments as there are dimensions in the vector. The dimensionality is defined by a template when instantiating the vector. All augments should be able to be converted into the same data type from which the vector originates (defined in the template to).

This is what I have written so far (I used many snippets from other questions here):

// this section is to create an typedef struct all_same which checks if all elements are of the same type

template<typename ...T>  // head template
struct all_same : std::false_type { };

template<>  // accept if no elements are present
struct all_same<> : std::true_type { };

template<typename T>  // accept if only one element is present
struct all_same<T> : std::true_type { };

template<typename T, typename ...Ts>  // check if first and second value are the same type and recurse
struct all_same<T, T, Ts...> : all_same<T, Ts...> { };



template<typename T, size_t N>
class vec_abs {
public:
    vec_abs(const vec_abs &vec);  // construct from another vector
    explicit vec_abs(const std::array<T, N> &arr);  // construct from array

    template<typename ...Ts> requires // only compile if:
            all_same<typename std::decay<Ts>::type...>::type  // all are the same type (this might be optional, no sure)
            && std::conjunction<std::is_convertible<Ts, T>...>::value &&  // all can be converted to T
            (sizeof(Ts) == N)  // there are N arguments
    explicit vec_abs(const Ts&&... values);  // construct from multiple arguments

private:
    std::array<T, N> data;
};

The fist section of the code tests if all arguments are of the same type, taken from this question.

I'm quite unsure about this solution (mainly because it's not working :D) and would appreciate any suggestions on improvements.

Thank you for your help!


Solution

  • If you do not want all arguments to be of same type you can remove that part. Apart from that you have a typo here:

    (sizeof(Ts) == N) 
    

    should be

    (sizeof...(Ts) == N) 
    

    After fixing that, forwarding the parameters and adding definition of the constructor, your code seems to do what you want:

    #include <type_traits>
    #include <array>
    #include <iostream>
    
    template<typename T, size_t N>
    struct vec_abs {
        template<typename ...Ts> requires // only compile if:
                std::conjunction<std::is_convertible<Ts, T>...>::value &&  // all can be converted to T
                (sizeof...(Ts) == N)  // there are N arguments
        explicit vec_abs(Ts&&... values) : data({std::forward<Ts>(values)...}) {}
        std::array<T, N> data;
    };
    
    int main() {
        auto foo = vec_abs<int,3>(1,2,3);
        foo = vec_abs<int,3>(1.0,2,3);    
        for (const auto& e : foo.data) std::cout << e;
    
        auto bar = vec_abs<std::string,3>("foo",std::string{"bar"},"moo");
        for (const auto& e : bar.data) std::cout << e;
        // bar = vec_abs<std::string,3>(123,1.2); // error wrong number
        //bar = vec_abs<std::string,3>(123,1.2,42); // error cannot be converted
    }
    

    Output::

    123foobarmoo
    

    In case you actually do want the constraint that all parameters are of same type...

    As the other answer mentions, the recursion in your all_same can be avoided. The following works already in C++11. Unfortunately I don't find the original source anymore.

    template <typename T,typename...Ts>
    struct all_same {
        static const bool value = std::is_same< all_same, all_same<Ts...,T>>::value;
    };