2

Let we have a struct Record{uint8_t x, y;};, a container Container<Record> of structs and a struct Transposed{Container<uint8_t> x,y};. Container c is a template which first arg is a type of value, all of the rest args have default values. For example it can be a std::vector (the rest args are types) or std::span (the rest arg is a value). The template should work with all of them. Also we may like to pass tne rest of template arguments to underlying template.

How we can get the Transposed from the container using templates?

I have tried variadic templates,

#include <iostream>
#include <vector>

template <typename value_type=uint8_t> struct Record{
    value_type x, y;
};

template<typename value_type, typename value_type2=uint8_t> class ContainerA: public std::vector<value_type>{
    value_type2 b=1u;
};

template<typename value_type, uint8_t int_value=1u> class ContainerB: public std::vector<value_type>{};

template<typename value_type, template <typename ...> typename container_type> class Transposed{
    container_type<value_type> x, y;

    public:
        Transposed(container_type<Record<value_type>> & recs){
            x.reserve(recs.size());
            y.reserve(recs.size());
            x.resize(recs.size());
            y.resize(recs.size());
            size_t i=0;
            for(auto &rec :recs){
                x[i] = rec.x;
                y[i] = rec.y;
                ++i;
            }
        }
};

int main(){
    std::vector<Record<uint8_t>> recsV{
        {1, 2},
        {3, 4}
    };
    Transposed trV{recsV};
    std::cout<<"vec"<<std::endl;

    ContainerA<Record<uint8_t>> recsA{
        {1, 2},
        {3, 4}
    };
    Transposed trA{recsA};
    std::cout<<"A"<<std::endl;

    /*ContainerB<Record<uint8_t>> recsB{
        {1, 2},
        {3, 4}
    };
    Transposed trB{recsB};
    std::cout<<"B"<<std::endl;*/
    return 0;
}

but it seems they cannot match both types and values. Usage of more than 1 variadic template argument is not allowed. Is it a flaw in C++ language or a deliberate design choice and do we need something like any_template_arg keyword or should just specifying 2 variadic arguments of different types be allowed to allow this use case?

max66
  • 65,235
  • 10
  • 71
  • 111
KOLANICH
  • 2,904
  • 2
  • 20
  • 20
  • Possible duplicate of [Is there any way of detecting arbitrary template classes that mix types and non-types?](https://stackoverflow.com/questions/39812789/is-there-any-way-of-detecting-arbitrary-template-classes-that-mix-types-and-non) – L. F. Sep 19 '19 at 12:24
  • Not quite. I have already read this question. Now the questions are if it is a design flaw in C++ and if we should fix it and how we should fix it. – KOLANICH Sep 19 '19 at 12:33
  • Well, judging the design of C++ is opinion-based (hence off topic for Stack Overflow) and that question already addresses how to fix it (you pretty much can't do that in a general way, but you can specialize for the specific containers). – L. F. Sep 19 '19 at 12:35

2 Answers2

1

As far I know, there isn't a way to match types and values (and template-template) template arguments together. And I've searched it for a long time.

So I don't see a way to make what do you want in a simple and elegant way.

Trying to respond to your question

How we can get the Transposed from the container using templates?

the best I can imagine is declare (isn't necessary define them taking in count are used only inside a decltype()) a couple of trivial functions as follows

template <typename VT,
          template <typename...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

template <typename VT,
          template <typename, auto...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

The first one is to remove the Record part when the CT container accepts a variadic list of types (std::vector and ContainerA cases); the second one is for CT containers accepting (after the type parameter) one or more values (ContainerB case).

Obviously this doen't cover all possible cases, but it's trivial declare other extract_func() functions to cover other cases.

Now you can declare Tranposed as follows

template <typename T,
          typename CT = decltype(extract_func(std::declval<T>()))>
class Transposed

Observe that, now, Transposed accept a generic type T but is SFINAE enabled only when the T type matches (as argument) an extract_func() declaration.

In the Transposed body you can use CT to declare x and y and T for the argument of the constructor.

The following is a full compiling example

#include <iostream>
#include <vector>

template <typename value_type=std::uint8_t>
struct Record
 { value_type x, y; };

template <typename value_type, typename value_type2=std::uint8_t>
class ContainerA : public std::vector<value_type>
 { value_type2 b=1u; };

template <typename value_type, std::uint8_t int_value=1u>
class ContainerB : public std::vector<value_type>
 { };

template <typename VT,
          template <typename...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

template <typename VT,
          template <typename, auto...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

template <typename T,
          typename CT = decltype(extract_func(std::declval<T>()))>
class Transposed
 {
   private:
      CT x, y;

   public:
      Transposed (T & recs)
       {
         x.reserve(recs.size());
         y.reserve(recs.size());
         x.resize(recs.size());
         y.resize(recs.size());
         std::size_t i=0u;
         for(auto &rec :recs){
            x[i] = rec.x;
            y[i] = rec.y;
            ++i;
         }
       }
 };

int main ()
 {
   std::vector<Record<std::uint8_t>> recsV { {1, 2}, {3, 4} };

   Transposed trV{recsV};

   std::cout<<"vec"<<std::endl;

   ContainerA<Record<std::uint8_t>> recsA { };

   Transposed trA{recsA};

   std::cout<<"A"<<std::endl;

   ContainerB<Record<std::uint8_t>> recsB { };

   Transposed trB{recsB};

   std::cout<<"B"<<std::endl;
 }
max66
  • 65,235
  • 10
  • 71
  • 111
0

One working example for you:

template<typename value_type=uint8_t>
struct Record{
    value_type x, y;
};

template<class T>
std::vector<T> rebind_container(std::vector<Record<T>> const&);

// Span transposes into vector.
template<class T>
std::vector<T> rebind_container(std::span<Record<T>> const&); 

template<class T>
std::list<T> rebind_container(std::list<Record<T>> const&);

inline void reserve(...) {}

template<class... Args>
inline void reserve(std::vector<Args...>* v, size_t n) {
    v->reserve(n);
}

template<class container_type>
struct Transposed {
    using container_type2 = decltype(rebind_container(std::declval<container_type>()));

    container_type2 x, y;

    Transposed(container_type const& recs) {
        auto const n = recs.size();
        reserve(&x, n);
        reserve(&y, n);
        for(auto& rec : recs) {
            x.push_back(rec.x);
            y.push_back(rec.y);
        }
    }
};

int main(){
    std::vector<Record<uint8_t>> recsV{
        {1, 2},
        {3, 4}
    };
    Transposed<decltype(recsV)> trV{recsV};
    std::cout << trV.x.size() << std::endl;

    std::list<Record<uint8_t>> recsV2{
        {1, 2},
        {3, 4}
    };
    Transposed<decltype(recsV2)> trV2{recsV2};
    std::cout << trV2.x.size() << std::endl;
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 1
    That's `vector` only. The question is asking for a generic one. – L. F. Sep 19 '19 at 12:36
  • @L.F. True. You need to add `rebind_container` functions for different containers, as well as different specialisations of `Transposed` for different containers (e.g. `std::list` doesn't have `reserve` and indexed access). `std::span` won't work for you because it doesn't own storage. – Maxim Egorushkin Sep 19 '19 at 12:38
  • @KOLANICH It works for `vector` and `list`. That should be enough for you to see how to support other containers you may want. `std::span` won't work for you because it doesn't store anything. – Maxim Egorushkin Sep 19 '19 at 12:46
  • 1
    Yeah, std::span doesn't have reserve, so it requires specialization. But it doesn't change the main problem of the question - inability to match templates against any template. – KOLANICH Sep 19 '19 at 12:46
  • @KOLANICH You can use `std::span` as input only. Added a specialization for you, as another example. – Maxim Egorushkin Sep 19 '19 at 14:22