20

I would like to have a template class (e.g. float/double type), but I am using Nvidia CUDA and OptiX and have multiple other types (e.g. float2, double2, float3,...) that depend on the chosen template type.

Something like this:

#include <optixu/optixu_vector_types.h>
#include <type_traits>

template <class T>
class MyClass 
{
   MyClass()
   {
      if (std::is_same<T, float>::value) 
      {
         typedef optix::float2 T2;
      }
      else if (std::is_same<T, double>::value)
      {
         typedef optix::double2 T2;
      }

      T2 my_T2_variable;
   }

   void SomeFunction() 
   { 
      T2 another_T2_variable; 
   };
};

My solution for now is to have multiple template arguments MyClass<T,T2,T3> my_object;, but this seems to have too much overhead and clutter. Is there a way to achieve the same with a single template argument as desired above?

JeJo
  • 30,635
  • 6
  • 49
  • 88
SemtexB
  • 660
  • 6
  • 21

3 Answers3

21

Typically you'd do this by creating a trait type whose specializations define the additional types. For example:

// Base template is undefined.
template <typename T>
struct optix_traits;

template <>
struct optix_traits<float> {
    using dim2 = optix::float2;
    // etc
};

template <>
struct optix_traits<double> {
    using dim2 = optix::double2;
    // etc
};

Then you can alias from these types to a name in your type, if desired:

template <typename T>
class MyClass {
public:
    using T2 = typename optix_traits<T>::dim2;
};
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • That worked. Thanks. On a minor note: I just noticed that I have private member functions that return `T2`/`T3` and it throws an error about the unkown (return) type `T2`? E.g. `T2 MyClass::Foo(){return my_T2_variable;}` Can this be resolved somehow? Or shall I post a new question? – SemtexB Jul 07 '20 at 08:07
  • 2
    @SemtexB An out-of-class member function definition is not "in scope" until after it knows what type the member belongs to. When it sees `T2` there it doesn't know what `T2` is because it doesn't know the method is in `MyClass` yet. Spell it all out: `template typename MyClass::T2 MyClass::Foo() { ... }` Since C++11 you can also use `auto` to move the return type, like this: `template auto MyClass::Foo() -> T2 { ... }`. Since the return type appears after `MyClass::` in that case, it knows to look in `MyClass` for it. – cdhowie Jul 07 '20 at 08:09
  • Perfect, that worked. Thanks for the fast reply. I tried `template MyClass::T2 MyClass::Foo() { ... }`, but forgot the `typename` keyword and template argument...doh! – SemtexB Jul 07 '20 at 08:20
  • 1
    Summary: whereas all templates are meta-functions, type traits are the specific idiom we use when we want to define them more-or-less by enumerating all the possibilities, and where the outputs of the function(s) are a bunch of "facts" about the type (or you can call them properties or *traits* of the type). So, a simple mapping from a list of types to a list of other types pretty much *is* a type trait whether you think to name it that or not. – Steve Jessop Jul 07 '20 at 16:34
15

You can use std::conditional, from <type_traits>.

If you want the T2 be optix::float2 when T == float and otherwise optix::double2, use std::conditional. This is availble since and will resolve the type T2 at compile time.

#include <type_traits>  // std::conditional, std::is_same

template <class T>
class MyClass
{
    using T2 = typename std::conditional<std::is_same<T, float>::value,
                                          optix::float2, optix::double2>::type;
    T2 my_T2_variable;

    // ... other code
};

(See demo)


As @HikmatFarhat pointed out, std::conditional will not catch the user mistakes. It checks only the first condition, and for the false case gives the type optix::double2.

Another option is series of SFINAE ed functions, and decltype to those for the T2 as follows:

#include <type_traits>  // std::is_same, std::enable_if

template <class T> // uses if T == float and return `optix::float2`
auto typeReturn() -> typename std::enable_if<std::is_same<float, T>::value, optix::float2>::type { return {}; }

template <class T> // uses if T == double and return `optix::double2`
auto typeReturn() -> typename std::enable_if<std::is_same<double, T>::value, optix::double2>::type { return {}; }

template <class T>
class MyClass
{
    using T2 = decltype(typeReturn<T>()); // chooses the right function!

    T2 my_T2_variable;

    // ... other codes
};

(See demo)

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • 1
    yes but if he makes a mistake std::conditional will not catch it. For example calling it with std::string it defaults to optix::double2. Whereas the type_trait solution will give an error. – Hikmat Farhat Jul 07 '20 at 08:23
  • 3
    @HikmatFarhat You are right. Added another option, which is similar to the type_trait solutions and should cover the issue. – JeJo Jul 07 '20 at 08:44
6

Implement a meta-function using template specialization that maps standard C++ types to OptiX types with the desired "rank":

template <typename T, std::size_t N> struct optix_type;

template <> struct optix_type<float, 2> { using type = optix::float2; };
template <> struct optix_type<float, 3> { using type = optix::float3; };
template <> struct optix_type<double, 2> { using type = optix::double2; };
// ...

template <typename T, std::size_t N>
using optix_type_t = typename optix_type<T, N>::type;

You can then use this within your class(es) to easily get the right types:

template <class T>
class MyClass {
  using T2 = optix_type_t<T, 2>;
  MyClass() {
    T2 my_T2_variable;
    optix_type_t<T, 3> my_T3_variable;
  }
  void SomeFunction() { T2 another_T2_variable; };
};
Erlkoenig
  • 2,664
  • 1
  • 9
  • 18