3
template<typename T, int V>
class AwesomeClass    // [1]
{
public:

    AwesomeClass(T baseValue)
        : m_value(baseValue + V)
    {
    }

    T get() const
    {
        return m_value;
    }

private:

    T m_value;
};

#ifdef USE_ALIAS_TEMPLATES    // [2]

template<typename T>
using AwesomeClassWithOne = AwesomeClass<T, 1>;

template<typename T>
using AwesomeClassWithTwo = AwesomeClass<T, 2>;

#else    // [4]

template<typename T>
class AwesomeClassWithOne: public AwesomeClass<T, 1>
{
    using AwesomeClass<T, 1>::AwesomeClass;
};

template<typename T>
class AwesomeClassWithTwo: public AwesomeClass<T, 2>
{
    using AwesomeClass<T, 2>::AwesomeClass;
};

AwesomeClassWithOne(int) -> AwesomeClassWithOne<int>;
AwesomeClassWithTwo(int) -> AwesomeClassWithTwo<int>;

#endif

int main()
{
    AwesomeClassWithOne firstObj(20);    // [3]
    AwesomeClassWithTwo secondObj(20);

    return firstObj.get() + secondObj.get();
}

I have a class, AwesomeClass [1], which takes two template parameters, typename T and int V. V is an internal detail that I don't want to expose to users of the class, so instead I want to provide aliases such as AwesomeClassWithOne and AwesomeClassWithTwo which take only T as a template parameter, and have V already bound to some value (1 and 2 respectively in my example).

Alias templates seemed to be appropriate for this [2]. However, as of C++17, it appears that argument deduction cannot be used with alias templates, so I can't do [3].

So I came up with an alternative [4] which involves creating new derived classes for each "alias" type I want, inheriting all the constructors from the base class, then creating deduction guides to make automatic deduction work (base class constructors don't seem to result in automatic deduction guides like normal constructors do; related question).

Does this seem like an appropriate workaround? Can I expect to see any weird side-effects compared to the alias template solution?

TripShock
  • 4,081
  • 5
  • 30
  • 39

2 Answers2

1

It's a pity that C++17 doesn't accept deduction guides for type aliases.

But you can ever use the good old make-something way (std::make_pair(), std::make_tuple(), std::make_unique(), etc.).

By example: if you define, switching the order of template arguments, a makeAwesome() function as follows

template <int V, typename T>
AwesomeClass<T, V> makeAwesome (T && t)
 { return { std::forward<T>(t) }; 

you can obtain an AwesomeClass<T, V> object, explicating V and deducing T, calling it as follows

auto firstObj = makeAwesome<1>(20);

If you don't want expose to the user the integer V part, you can add

template <typename T>
auto makeAwesomeWithOne (T && t)
 { return makeAwesome<1>( std::forward<T>(t) ); }

template <typename T>
auto makeAwesomeWithTwo (T && t)
 { return makeAwesome<2>( std::forward<T>(t) ); }

and in main() you can write

auto firstObj  = makeAwesomeWithOne(20);
auto secondObj = makeAwesomeWithTwo(20);

I know that it's annoying to write a lot of makeAwesomeWith-number functions that are almost equals.

If the use of old C-style function macros doesn't scare you (they are distilled evil, IMHO, but in circumstances like this...) you can auto-magically create the makeAwesomeWith-number functions as follows

#define MakeAwesomeWith(Str, Num) \
   template <typename T> \
   auto makeAwesomeWith ## Str (T && t) \
    { return makeAwesome<Num>( std::forward<T>(t) ); }

MakeAwesomeWith(One, 1);
MakeAwesomeWith(Two, 2);
// ...

The following is a full working (macro based) example

#include <iostream>
#include <utility>

template <typename T, int V>
class AwesomeClass
 {
   public:
      AwesomeClass (T baseValue) : m_value(baseValue + V)
       { }

      T get () const
       { return m_value; }

   private:
      T m_value;
 };

template <int V, typename T>
AwesomeClass<T, V> makeAwesome (T && t)
 { return { std::forward<T>(t) }; }

#define MakeAwesomeWith(Str, Num) \
   template <typename T> \
   auto makeAwesomeWith ## Str (T && t) \
    { return makeAwesome<Num>( std::forward<T>(t) ); }

MakeAwesomeWith(One, 1);
MakeAwesomeWith(Two, 2);

int main ()
 {
   auto firstObj  = makeAwesomeWithOne(20);
   auto secondObj = makeAwesomeWithTwo(20);

   std::cout << (firstObj.get() + secondObj.get()) << std::endl;
 }
max66
  • 65,235
  • 10
  • 71
  • 111
1

Use alias templates.

Class template argument deduction doesn't work with alias templates (and unclear what that could mean), but it's not exactly clear that class template argument deduction works with inherited constructors either. It's still an open question and needs to be addressed.

Moreover, you don't actually want to introduce a new type anyway. You very much want AwesomeClass<T, 1> to be the type. So it could lead to problems if AwesomeClassWithOne<T> and AwesomeClass<T, 1> were actually different. These aren't the kind of problems you really want to get yourself into, especially if your sole reason for doing this is to allow for class template argument deduction.

If you really need that feature, just provide make_*() helpers like we're used to having to write.

Barry
  • 286,269
  • 29
  • 621
  • 977