Home > front end >  How do I refer to a class with multiple template types by only one of the types?
How do I refer to a class with multiple template types by only one of the types?

Time:04-03

Let's say I have a class Shape with the declaration:

template <typename T, typename U, typename V>
class Shape {
   T value;
   U input;
   V input2;

   ...
}

As it is, if I create a Shape object, its type will be something like Shape<int, float, double> - for example.

But what if I want to be able to create a Shape object and still give it inputs of different types (like float and double), but I want its type to be Shape<int>.

That is, after creating a Shape object, I want callers to only care about the type of its value, not the type of its inputs. How do you suggest I go about it?

I've learned that I cannot store templates member variables without having declared the template types at the top. I've also attempted using an alias like:

template <typename T, typename U, typename V>
using ShapeAlias<T> = Shape<T, U, V>

But that doesn't work either. Do you have any suggestions on how I can go about this?

I'm considering some form of inheritance where there's a base class with only one of the types, and the derived class contains all three types, but I thought I should check here.

Edit: I need the second and third types because the idea is that a user will be able to pass a function to a Shape constructor to calculate the value of T, which could look like:

auto creator = [](U a, V b){
   return (something of type T)
}

And I want to keep the values of the input types in the Shape class.

So from a client's perspective, their code should look like this:

Shape<T> shapeA(Shape<T>(uValue, vValue, creator)

As it is now, they would have to do:

Shape<T, U, V> shapeA(Shape<T, U, V>(uValue, vValue, creator))

CodePudding user response:

It seems like you are looking for CTAD (class template argument deduction). It only works when the caller does not specify any template argument, hence a layer of indirection has to be added:

template <typename T>
struct ShapeWrap {
    template <typename U,typename V>
    struct Shape {
        T value;
        U input;
        V input2;
        Shape(const U& u,const V& v) : input(u),input2(v) {}
    };
};

Caller can now call:

auto s = ShapeWrap<int>::Shape(1.0,0.1f);

To instantiate ShapeWrap<int>::Shape<double,float>.


Alternatively via template argument deduction of a function template:

template <typename T, typename U,typename V>
struct Shape {
    T value;
    U input;
    V input2;
    Shape(const U& u,const V& v) : input(u),input2(v) {}
};

template <typename T,typename U,typename V>
Shape<T,U,V> make_shape(const U& u, const V& v) {
    return {u,v};
}



int main() {
    auto s = make_shape<int>(1.0,0.1f);
}

CodePudding user response:

If the creator object is only used in the constructor itself, but you don't need to store the values for later use, you could just resort to a templated constructor. If you do need to store the value the full type including all type parameters need to be specified, if the user has to name the type themselves. You could design the class in a way for auto to be useable.

Alternative 1: Create a function for creating the object.

This allows the user to specify some of the template parameters starting from the left, but having the rest deduced by the compiler.

template<class Product, class CreatorType, class ...Args>
class Factory
{
public:
    Factory(CreatorType const& creator, Args...args)
        : m_creator(creator), m_arguments(args...)
    {
    }

    Product Create()
    {
        return std::apply(m_creator, m_arguments);
    }
private:
    CreatorType m_creator;
    std::tuple<Args...> m_arguments;
};

template<class Product, class CreatorType, class ...Args>
auto CreateFactory(CreatorType const& creator, Args ... args)
{
    return Factory<Product, CreatorType, Args...>(creator, args...);
}

int main() {

    auto creator = [](int value) -> int { return value   1; };

    auto factory = CreateFactory<long>(creator, 41); // we're able do add a type conversion int -> long here

    std::cout << "The answer is " << factory.Create() << '\n';
}

Alternative 2: Add a class template argument deduction (CTAD) guideline

If you're ok with having the return type automatically be deduced based on the result of invoking the creator, you could create a CTAD guideline automatically deducing the type parameters, if the user does not specify any of the template parameters.

template<class Product, class CreatorType, class ...Args>
class Factory
{
public:

    Factory(CreatorType const& creator, Args const&...args)
        : m_creator(creator), m_arguments(args...)
    {
    }

    Product Create()
    {
        return std::apply(m_creator, m_arguments);
    }
private:
    CreatorType m_creator;
    std::tuple<Args...> m_arguments;
};

// deduction guideline: the result of calling creator with the arguments is used as the first template parameter
template<class CreatorType, class ... Args>
Factory(CreatorType const&, Args const&...) -> Factory<decltype(std::apply(std::declval<CreatorType>(), std::declval<std::tuple<Args...>>())), CreatorType, Args...>;

int main() {
    auto creator = [](int value) -> int { return value   1; };

    Factory factory(creator, 41); // type of factory automatically chosen to be Factory<int, ..., int>

    std::cout << "The answer is " << factory.Create() << '\n';
}
  • Related