Linked list of template type with the next pointer being a different specialization

93 Views Asked by At

I would liked to have a class template with a single type parameter, and inside the class hold a pointer to the next class in the linked-list, but allow that class to potentially be of a different type (in this example the different type is a specialization.)

I tried this code:

template<typename T, typename U>
class Banana
{
    public:
        void function(T parameter); // illustrates class methods/members only concerned with single type T

        Banana<U>* next;
}

The compiler gives error:

'Banana':too few template arguments.

4

There are 4 best solutions below

6
bitmask On

I gather you want to do this statically. One way to do this is to specify a succession order. In the example below I have composed the type, but this will obviously not work if you have loops in your succession order. In that case, use a std::unique_ptr instead.

template <typename V>
struct SuccessorMaker { using type = V; };

template <typename> struct Successor : SuccessorMaker<void> {}; // end
// specify succession order here
template <> struct Successor<int> : SuccessorMaker<float> {};
template <> struct Successor<float> : SuccessorMaker<short> {};

template <typename T>
struct Banana {
  void function(T);
  Banana<typename Successor<T>::type> next;
};
template <> struct Banana<void> {}; // end of list

int main() {
    Banana<int> banana;
    banana.function(1);
    banana.next.function(2.f);
}

(demo)

You can of course also do this with polymorphism by having a common base class. This would be just a regular list.


Edit: Here's a way to do it dynamically at runtime. It is a bit more cumbersome to instantiate but it allows you the flexibility to have arbitrary lists. See the updated link to the demo above.

template <typename... Ts>
struct CherryTree {
  template <typename T>
  struct Cherry {
    void function(T);
    template <typename U>
    auto& setNext() {
        auto ptr = std::make_unique<Cherry<T>>();
        auto const res = ptr.get();
        next = std::move(ptr);
        return *res;
    }
    std::variant<std::unique_ptr<Cherry<Ts>>...> next;
  };
};

int main() {
    using Tree = CherryTree<int,float,short>;
    Tree::Cherry<int> cherry;
    cherry.function(1);

    //cherry.next = std::make_unique<Tree::Cherry<float>>();
    auto& next = cherry.setNext<float>();
    next.function(2.f); // call after reset
    std::visit([](auto& n) {n->function(3.f);}, cherry.next); // regular call
}
1
user202474 On

Thank you @Bitmask and others.

I have come up with an alternative approach.

I have a non-template base class and for it I declare a virtual function for all types I will use. I define empty functions for all these types, since I don't use them. I instead use a derived class, which has a base class pointer to the next block.

The derived block is templated by its type and its neighbours type. I override the base function for its type.

The downsides are having to explicitly declare the needed types in the base class, and explicitly give an empty implementation. For my purposes this is too bad.

#include <iostream>
#include <typeinfo>

#include<string>

class BaseBlock
{
    public:

        BaseBlock* next;

        virtual void Apply(char sample);
        virtual void Apply(int sample);
        virtual void Apply(float sample);
        virtual void Apply(double sample);
        virtual void Apply(short sample);
        virtual void Apply(std::string sample);
};


template <typename T, typename U>
class DerivedBlock : public BaseBlock
{
    public:

        void Apply(T sample);
};


template <typename T, typename U>
void::DerivedBlock<T, U>::Apply(T sample)
{
    std::cout << "DerivedBlock::Apply - typeid: " << typeid(sample).name() << "   value: " << sample << std::endl << std::endl;

    if (next != 0)
    {
        next->Apply((U)sample);
    }
}


template <>
void::DerivedBlock<short, std::string>::Apply(short sample)
{
    std::cout << "DerivedBlock::Apply - typeid: " << typeid(sample).name() << "   value: " << sample << std::endl << std::endl;

    if (next != 0)
    {
        next->Apply(std::to_string(sample));
    }
}

template <>
void::DerivedBlock<std::string, int>::Apply(std::string sample)
{
    std::cout << "DerivedBlock::Apply - typeid: " << typeid(sample).name() << "   value: " << sample << std::endl << std::endl;

    if (next != 0)
    {
        next->Apply(std::stoi(sample));
    }
}

int main()
{

    DerivedBlock<int, float> db_int_float;
    DerivedBlock<float, char> db_float_char;
    DerivedBlock<char, int> db_char_int;
    DerivedBlock<int, double> db_int_double;
    DerivedBlock<double, short> db_double_short;
    DerivedBlock<short, std::string> db_short_stdString;
    DerivedBlock<std::string, int> db_stdString_int;

    db_int_float.next = &db_float_char;
    db_float_char.next = &db_char_int;
    db_char_int.next = &db_int_double;
    db_int_double.next = &db_double_short;
    db_double_short.next = &db_short_stdString;
    db_short_stdString.next = &db_stdString_int;
    db_stdString_int.next = 0; // End of list

    db_int_float.Apply(83U);

    return 0;
}


void BaseBlock::Apply(char sample) {};
void BaseBlock::Apply(int sample) {};
void BaseBlock::Apply(float sample) {};
void BaseBlock::Apply(double sample) {};
void BaseBlock::Apply(short sample) {};
void BaseBlock::Apply(std::string sample) {};

Produces:

enter image description here

0
bbalazs On

Based on your own answer, it seems to me that you want a pipeline processing. If it's not the case, feel free to comment and I'll remove this answer.

If you don't insist on using classes, here's a free-function approach. In order to support different pipelines, I introduced a Pipeline enum.

The apply() functions in the detail namespace do the conversion/processing, the pipeline() function is responsible for the iteration.

enum class Pipeline
{
    A,
    B,
    C
};

namespace detail
{
    template <Pipeline id, typename Input, typename Output>
    Output apply(Input);
    
    template <>
    float apply<Pipeline::A, int, float>(int i)
    {
        return i;
    }

    template <>
    std::string apply<Pipeline::A, float, std::string>(float f)
    {
        return std::to_string(f);
    }
}


template <Pipeline id, typename Input, typename Output, typename... Ts>
auto pipeline(Input input)
{
    Output output = detail::apply<id, Input, Output>(input);
    
    if constexpr (sizeof...(Ts) > 0)
    {
        return pipeline<id, Output, Ts...>(output);
    }
    else
    {
        return output;
    }
}

int main()
{
    std::cout << pipeline<Pipeline::A, int, float, std::string>(64) << std::endl;
    return 0;
}

Output:

64.000000
0
n. m. could be an AI On

If you want to chain functions, you may like something like this:

#include <functional>
#include <string>

template <typename A, typename B>
struct Func
{
    Func(std::function<A(B)> func) : func(func) {};
    A operator()(B b) const { return func(b); }

    std::function<A(B)> func;
};

template <typename A, typename B, typename C>
Func<A,C> operator|(Func<A,B> ab, Func<B,C> bc)
{
    return Func<A,C>([ab,bc](C c)->A{return ab(bc(c)); });
}

int main()
{
    Func<int, double> f ([](double d) { return (int) d; });
    Func<std::string, int> g ([](int i) { return std::to_string(i); });
    Func<int, std::string> h ([](std::string s) { return std::stoi(s); });

    auto chain = h | g | f;
}