2

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!

  • 2
    do you want them all the be of same type (that can be converted to `T`) or just all be convertible to `T` ? – 463035818_is_not_an_ai Apr 23 '21 at 12:47
  • @largest_prime_is_463035818 I would prefer them just to be convertible to `T`, I just was not able to get that working (Also wow that response was quick!) – RedNicStone Apr 23 '21 at 12:55
  • 2
    When you say "it's not working" can you be more specific, with examples and expectations, etc. – Chris Uzdavinis Apr 23 '21 at 12:59
  • You have a typo in `(sizeof...(Ts) == N)` otherwise the templates you posted compile: https://godbolt.org/z/Ys11ffqWq. Maybe there are more problems in the definitions and when you instantiate it, but you didnt show that. Please include a [mcve] – 463035818_is_not_an_ai Apr 23 '21 at 13:04
  • If you accept that all `Ts...` are convertible to `T`, maybe you can also accept that are different. – max66 Apr 23 '21 at 13:14
  • its not quite clear why added the constraint of all being equal, when thats not necessarily what you want. Did you add it to solve some previous issue? – 463035818_is_not_an_ai Apr 23 '21 at 13:32
  • @largest_prime_is_463035818 After removing and fixing the issues regarding the typecheck as described by @ChrisUzdavinis, the compiler still fails at the point you described (eg. `(sizeof...(Ts) == N)`). I am unsure how I should check that the number of arguments are the same as the dimensionality. – RedNicStone Apr 23 '21 at 13:36
  • @largest_prime_is_463035818 yes, I used that code previously because i was unable to check if *All* arguments were convertible to `T`, but since that is solved now there is no use for it – RedNicStone Apr 23 '21 at 13:37
  • 1
    your code has `(sizeof(Ts) == N)` but you want the `sizeof...` operator, thats `(sizeof...(Ts) == N)` – 463035818_is_not_an_ai Apr 23 '21 at 13:42

2 Answers2

1

Recursive templates are very expensive (in terms of compile time and compiler memory-use) so while they work, and historically were the only way to do some solutions, more modern techniques are now available that are more succinct and compile much faster.

You can replace your entire all_same with a simple constexpr function using a fold expression:

template <typename T, typename... Ts>
constexpr bool all_same() {
    return (std::is_same_v<T, Ts> && ...);
}

static_assert( all_same<int, int, int>() );

However, given that they all must be convertible to T, why do you also want the ensure they are the exact same type? That seems needlessly restrictive. You could easily change the function above to "all_convertible_to" using std::is_convertible_v<From, To> instead.

Chris Uzdavinis
  • 6,022
  • 9
  • 16
  • First, Thanks for your help and quick response! I would prefer using `std::is_convertible_v`, the elements don't have to be of the same type. – RedNicStone Apr 23 '21 at 13:04
1

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;
};
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185