4

I am aware this question is quite similar to This post, however the cited post was about class template argument deduction. Here my question is about function template argument deduction.

I currently have the following piece of code, which enables users to specify the algorithm a method should use in order to compute some values. (Using a strategy Pattern is not applicable in this case for various reasons which are not relevant here)

#include <iostream>
#include <typeinfo>
template<typename T>
class BasicStrategy
{
    public:
    BasicStrategy(T& i_Input)
    {
        std::cout << "Applying Basic Strategy to type : " << typeid(T).name() << " With value : " << i_Input <<std::endl;
    }
};

template<typename T>
class ComplexStrategy
{
    public:
    ComplexStrategy(T& i_Input)
    {
        std::cout << "Applying Complex Strategy to type : " << typeid(T).name() << " With value : " << i_Input <<std::endl;
    }
};

template<typename T, typename STRATEGY = BasicStrategy<T>>
void Func(T& i_Input)
{
    STRATEGY MyStrategy(i_Input);
}


int main()
{
    int i = 12;
    double d = 24;
    
    Func(i);
    Func(d);

    Func<int, ComplexStrategy<int>>(i);
    Func<double, ComplexStrategy<double>>(d);
    return 0;
}

I would like to know if it would be possible to simplify the interface of "Func()" in order to exempt the user from specifying redundant types if not using the "BasicStrategy" For instance, the "ideal" interface would look like this (i realize this is not possible though):

int main()
{
    int i = 12;
    double d = 24;

    Func(i);
    Func(d);

    Func<ComplexStrategy>(i);
    Func<ComplexStrategy>(d);
    return 0;
}

Of course, i could declare Func() like this

template<typename STRATEGY, typename T>
void Func(T& i_Input)

Which would not require the user to specify the type T twice, however, this prevents me from assigning a default strategy to the method, which would break a lot of existing code and making it less readable overall.

Is there a clean solution to this problem or is this a choice i have to make between the two options?

J.M
  • 313
  • 1
  • 8

4 Answers4

6

You can use a template template parameter to achieve exactly what you want:

template<template <typename> typename Strategy = BasicStrategy, typename T>
void Func(T& i_Input)
{
    Strategy<T> MyStrategy(i_Input);
}

That way what you mentioned as ideal would work: https://godbolt.org/z/aosPzqa3r

Matthias Grün
  • 1,466
  • 1
  • 7
  • 12
  • Awesome, i've never used these before, this would have saved me so many headaches if i knew this before... Small question though : i have seen answers where the template template argument was given a name (for instance template – J.M Jan 24 '23 at 14:25
  • Yes, they're quite handy. I was amazed when I learned about them, too :-) – Matthias Grün Jan 24 '23 at 14:31
  • @J.M As far as I know there is no difference, the name is optional according to https://en.cppreference.com/w/cpp/language/template_parameters#Template_template_parameter – Matthias Grün Jan 24 '23 at 14:34
  • I still have a question though (if necessary, i would make another post), If the "Strategies" have an unspecified number of arguments (For instance, lets say the user wants to create a strategy that takes a std::ratio and a typename T in order to store i_Input and later multiply it with the std::ratio), is there a way to make the template template accept an unspecified number of parameters? For instance using parameter packs? – J.M Jan 24 '23 at 14:59
  • @J.M Yes, you can just write `template typename Strategy` – Matthias Grün Jan 24 '23 at 15:05
1

i realize this is not possible though

It is when you make STRATEGY a template template argument.

#include <iostream>
#include <typeinfo>
template<typename T>
class BasicStrategy
{
    public:
    BasicStrategy(T& i_Input)
    {
        std::cout << "Applying Basic Strategy to type : " << typeid(T).name() << " With value : " << i_Input <<std::endl;
    }
};

template<typename T>
class ComplexStrategy
{
    public:
    ComplexStrategy(T& i_Input)
    {
        std::cout << "Applying Complex Strategy to type : " << typeid(T).name() << " With value : " << i_Input <<std::endl;
    }
};
template <template<typename> typename STRATEGY = BasicStrategy, typename T>
void Func(T& i_Input)
{
    STRATEGY<T> MyStrategy(i_Input);
}


int main()
{
    int i = 12;
    double d = 24;
    
    Func(i);
    Func(d);

    Func<ComplexStrategy>(i);
    Func<ComplexStrategy>(d);
    return 0;
}

Live Demo

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

C++20 Answer:

Using a template template parameter for the strategy and auto for the function parameter allows you to get the syntax you want. Doing so gives you

template<template<typename> typename STRATEGY = BasicStrategy>
void Func(auto& i_Input)
{
    STRATEGY MyStrategy(i_Input);
}

Using your example of

int main()
{
    int i = 12;
    double d = 24;
    
    Func(i);
    Func(d);

    Func<ComplexStrategy>(i);
    Func<ComplexStrategy>(d);
    return 0;
}

it outputs

Applying Basic Strategy to type : i With value : 12
Applying Basic Strategy to type : d With value : 24
Applying Complex Strategy to type : i With value : 12
Applying Complex Strategy to type : d With value : 24
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
1

You may switch to use functor separating Strategy from T:


template<template<typename T> typename STRATEGY = BasicStrategy>
struct t_Func
{
    template<typename T>
    void operator ()(T& i_Input) const
    {
        STRATEGY<T> MyStrategy(i_Input);
    }
};

template<template<typename T> typename STRATEGY = BasicStrategy>
constexpr t_Func<STRATEGY> Func{};

int main()
{
    int i = 12;
    double d = 24;
    
    Func<>(i);
    Func<>(d);

    Func<ComplexStrategy>(i);
    Func<ComplexStrategy>(d);
    return 0;
}

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84