C++ Type, Function, And Value Aliasing For Potentially Undefined Types

Viewed 92

Suppose I have two libraries, let's arbitrarily call them "CUDA" and "HIP". These two libraries just so happen to have eerily similar interfaces, and I want to wrap these interfaces into a templated class where the template parameter defines which library is called. My current implementation is (in an abridged form):

#define ALIAS_FUNCTION(Alias_,Original_)              \
  template <typename... Args> Alias_(Args&&... args)  \
  -> decltype(Original_(std::forward<Args>(args)...)) \
  { return Original_(std::forward<Args>(args)...); }

enum class Selector {CUDA,HIP};

template <Selector T> struct Interface;

#if (havecuda)
#  include <cuda_headers.h>
template <>
struct Interface<Selector::CUDA>
{
  using error_type = cudaError_t;
  static const auto success = cudaSuccess;

  ALIAS_FUNCTION(static constexpr getLastError,cudaGetLastError);
};
#endif

#if (havehip)
#  include <hip_headers.h>
template <>
struct Interface<Selector::HIP>
{
  using error_type = hipError_t;
  static const auto success = hipSuccess;

  ALIAS_FUNCTION(static constexpr getLastError,hipGetLastError);
};
#endif

While this works it is extremely painful to maintain, as I need to duplicate every alias, as well as make sure a fair chunk of boilerplate is correct. Not to mention the macros. What I want ideally is keep it all in one definition so something like:

template <Selector T>
struct Interface
{
  using error_type = std::conditional<T==Selector::CUDA,cudaError_t,hipError_t>;
  // do something similar to solve the function and variable aliasing
};

but alas std::conditional requires both types to be at least declared. Is there a cleverer solution here? It should ideally also be C++11.

edit: (just to answer the comments questions more visibly) The idea of the Interface class is that there are other classes which derive from it (assuming one of havecuda or havehip or both is true) in order to gain access to the types:

template <Selector T>
class DoWork : Interface<T>
{
  using typename Interface<T>::error_type;
  using Interface<T>::success;
  using Interface<T>::getLastError;

  void work()
  {
    error_type err;
    if (getLastError() != success) {
      // something else
    }
  }
};

// instantiate the work classes if available
#if havecuda
template class DoWork<Selector::CUDA>;
#endif
#if havehip
template class DoWork<Selector::HIP>;
#endif
1 Answers

How about forming an overload set of the different implementations? This solution does not even require a class template (although you can still add it back in, if you don't mind the additional boilerplate).

#define TAGGED_ALIAS_FUNCTION(Tag_, Alias_,Original_)            \
  template <typename... Args> auto Alias_(Tag_, Args&&... args)  \
  -> decltype(Original_(std::forward<Args>(args)...))            \
  { return Original_(std::forward<Args>(args)...);}

namespace Selector {
    struct CUDA {};
    struct HIP {};
}

#if (havecuda)
#include <cuda_headers.h>
  TAGGED_ALIAS_FUNCTION(Selector::CUDA, static constexpr getLastError,cudaGetLastError);
#endif

#if (havehip)
  TAGGED_ALIAS_FUNCTION(Selector::HIP, static constexpr getLastError,hipGetLastError);
#endif

template<typename Selector_T>
void myFunction()
{
   auto const last_error = getLastError(Selector_T{});
} 
Related