1

If I have the simple class written using c++20 concepts


#include <array>
#include <type_traits>

template <class T, int N>
struct MyVec {
    std::array<T, N> m_vec;

    template <typename... Q>
    MyVec(Q&&... args) 
    requires (std::is_same_v<Q,T> && ...): 
    m_vec{std::forward<Q>(args)...} {}
};

and an example of it being used

int main(int argc, char* argv[]) {
    using V = MyVec<double, 2>;
    V v0(1., 2.);
    V v1(1., 2.);

    V v2 = v0;
}

The requires statement prevents the copy constructor from being hidden by the overly generic forwarding constructor. See https://godbolt.org/z/8Me19v7Ks

If you delete the requires statement then the line V v2 = v0 will fail to compile.

However I can't use concepts because this needs to be in a c++17 only context. I'm stuck trying to figure out how to use enable_if to protect the constructor. I could mark the construct as explicit but I'd prefer not to.

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • 1
    Don't you also want that `sizeof...(Q) == N`? – Jarod42 Mar 27 '23 at 13:41
  • Is it like a small non-related example? Otherwise why so complicated? `using V = std::array` in your `main` would suffice – Sergey Kolesnik Mar 27 '23 at 14:34
  • @SergeyKolesnik New here? https://stackoverflow.com/help/minimal-reproducible-example – bradgonesurfing Mar 27 '23 at 14:35
  • @Jarod42 the extra constraint might be a good idea though it would fail correctly fairly early on anyway and does not cause a problem for the other default constructors. – bradgonesurfing Mar 27 '23 at 14:37
  • would be nice to have a "minimal-viable" example, because there's also a trend about asking "XY questions" – Sergey Kolesnik Mar 27 '23 at 14:37
  • What are you going on about? The example is minimal and viable. It has a working example on godbolt. Exactly how this integrates into my million line long code base is not interesting to anybody. – bradgonesurfing Mar 27 '23 at 14:39
  • that is why I'm asking whether it is a "small non-related example". Because if it isn't, there's no need in overbloating your code base with useless wrappers. https://godbolt.org/z/ncWsqfvGj – Sergey Kolesnik Mar 27 '23 at 14:41
  • @SergeyKolesnik That was answered by holt already about 5 hours ago but thanks for the contribution – bradgonesurfing Mar 27 '23 at 14:45

2 Answers2

4

You can use enable_if to disable the template:

template <class T, int N>
struct MyVec {
    std::array<T, N> m_vec;

    template <typename... Q,
        std::enable_if_t<(std::is_convertible_v<Q, T> && ...), int> = 0>
    MyVec(Q&&... args) 
    : m_vec{std::forward<Q>(args)...} {}
};

I am not an expert with concepts, but most (all?) of what you can do with requires or similar, you can achieve with enable_if, although it's usually a bit uglier (and error prone) and will lead to much less readable errors from compilers.

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
Holt
  • 36,600
  • 7
  • 92
  • 139
  • That works. https://godbolt.org/z/5K438vEdM I've been using concepts so much the last 6 months I forgot how to do it old skool. – bradgonesurfing Mar 27 '23 at 09:39
1

One alternative I like about std::array wrapper is:

template <typename T, std::size_t>
using always_t = T;

template <class T, class Seq>
struct MyVecImpl;

template <class T, std::size_t... Is>
struct MyVecImpl<T, std::index_sequence<Is...>>
{
    std::array<T, sizeof...(Is)> m_vec;

    MyVecImpl(always_t<T, Is>... ts) : m_vec{std::move(ts)...}{}
};

template <class T, int N>
using MyVec = MyVecImpl<T, std::make_index_sequence<N>>;

Demo

that doesn't requires SFINAE/concepts.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • That is interesting. Why does this work? In that, how does this generate a parameter pack on *ts*. For example this doesn't work https://godbolt.org/z/6j967n4Tj I had always assumed that to generate a parameter pack one needs a template on the actual method. – bradgonesurfing Mar 27 '23 at 14:08
  • 1
    @bradgonesurfing `T` is not a parameter pack, so you cannot use `T...` whereas `Is` is, so you can use `Is...` and thus `always_t... `. Note that in this case you'd need both the `&&` and `const&` version if you want to achieve what you already have since you cannot use perfect forwarding with `always_t... `. – Holt Mar 27 '23 at 14:22