1

I was wondering how to achieve what I am describing below.
Consider a base CRTP class with a function that needs to be enabled and another one disabled.
This is controlled via a traits class dependent upon the definition of a type or not.
I preferably want to not have to define anything in my traits class but if it helps defining it as void I am OK with that. Further information in comments as it is much easier to show there what I am trying to achieve.

template<class T>
struct Model_Traits;

// Base CTRP class
template<typename Model>
class BaseModel
{
  inline const Model& impl() const { return static_cast<Model const&>(*this); }

  // not sure how to enable this template or not based on my traits class
  // which is necessary as some wont have it defined
  using foo_t = typename Model_Traits<Model>::foo_t;

  // this one should be enabled when foo_t is not defined
  void optimize(const foo1& f1, const foo2& f2, foo3* f3)
  {
    impl().optimize(f1,f2,f3);
  }

  // this one should only be enabled if foo_t is defined
  // not sure if this is correct
  template<typename T = foo_t,
           typename = std::enable_if<foo_t>::type>
  void optimize(const foo1& f1, const foo2& f2, foo3* f3, foo_t* f4)
  {
    impl().optimize(f1,f2,f3, f4);
  }
}

// First class defining the foo_t
template<MyModel>
struct Model_Traits
{
   using foo_t = myFoo;
}

class MyModel : public BaseModel<MyModel>
{
   void optimize(const foo1& f1, const foo2& f2, foo3* f3, foo_t* f4);
}

// Second class not defining foo_t
template<MyModel2>
struct Model_Traits
{

}

class MyModel2 : public BaseModel<MyModel2>
{
   void optimize(const foo1& f1, const foo2& f2, foo3* f3);
}

I should of course say that this is simplified and my code does not look like that but its pretty close if you take all the other stuff out.

Any ideas?

xerion
  • 303
  • 2
  • 11
  • Maybe you could use the void_t trick from "CppCon 2014: Walter E. Brown "Modern Template Metaprogramming: A Compendium". An example on how it could be used to implement concepts is shown here: http://stackoverflow.com/questions/26513095/void-t-can-implement-concepts Basically, it is a way to overload a template depending on whether something is defined or not. – KABoissonneault May 26 '15 at 12:36

3 Answers3

3

We can do this in a few steps. First, make a type trait for if foo_t is defined using void_t:

template <typename...>
using void_t = void;

template <typename T, typename = void>
struct has_foo_t : std::false_type { };

template <typename T>
struct has_foo_t<T, void_t<typename T::foo_t>> : std::true_type { };

Then we'll use this to forward our optimize function to something that is in a SFINAE-friendly context:

template <typename Model>
class BaseModel {
public:
    template <typename... Args>
    void optimize(Args&&... args) {
        optimize_impl<Model>(std::forward<Args>(args)...);
    }
};

We will have two impl functions: one enabled and one disabled:

template <typename T,
          typename = std::enable_if_t<has_foo_t<T>::value>>
void optimize_impl(const foo1& f1, const foo2& f2, foo3* f3, typename T::foo_t* f4)
{ .. }

template <typename T,
          typename = std::enable_if_t<!has_foo_t<T>::value>>
void optimize_impl(const foo1& f1, const foo2& f2, foo3* f3)
{ .. }

Even though T is Model in this case, we still need that as a function template argument in order to allow substitution failures to be "soft" failures instead of hard compile errors.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I do not see the need for perfect forward the arguments here to an _impl function. Am I missing something ? – xerion May 27 '15 at 12:59
  • 3
    @xerion Today, they take raw pointers. Tomorrow, who knows? Why *not* perfect forward them? – Barry May 27 '15 at 13:04
0

There is a conceptual mistake in your code, as you feed a type into the first parameter of the std::enable_if class (whereas it should be a Boolean):

typename = std::enable_if<foo_t>::type
  ...

(Moreover, a typename is missing in front of std::enable_if)

So instead of a type, your traits class should either contain a static constexpr bool which denotes whether the function should be enabled:

static constexpr bool enable_foo = Model_Traits<Model>::enable_foo;

which you could then pass to the std::enable_if (or more convenient: std::enable_if_t -- that alias let's you forget about the typename and the ::type).


By deriving from `std::enable_if`

Another direct workaround would be to derive your Traits-classes from std::enable_if:

template<T>
struct Model_Traits : public std::enable_if<false> {}

template<>
struct Model_Traits<MyModel> : public std::enable_if<true, myFoo> {}

template<>
struct Model_Traits<MyModel2> : public std::enable_if<false>
{
    //in principle you don't need to restate that, just for clarity
}

Then use it via

  template<typename T = foo_t,
           typename = typename Model_Traits<T>::type>
  void optimize(const foo1& f1, const foo2& f2, foo3* f3, foo_t* f4)
  {
      impl().optimize(f1,f2,f3, f4);
  }

That will enable the function only for MyModel, but not for MyModel2.


Using `void_t`

Furthermore, as mentioned in the comment, in a more modern style you can also use void_t:

template<class ...> using void_t = void;

struct A {};

struct B
{
    using foo_t=double;    
};

struct C
{
    template<typename T, typename = void_t<typename T::foo_t> >
    void foo(T const& t)
    {
        std::cout<<"hello"<<std::endl;
    }
};

int main()
{
    //C{}.foo(A{});  //compile time-error
    C{}.foo(B{});    //ok, prints "hello"
}

DEMO

This one is probably the most direct alternative. See also some related threads on SO.

Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • All of the answers were actually quite helpful as I was able to combine a few ideas and come up with something similar. In the end since I had another parameter that was defined optionally for the same function I opted to add a 'bool' variable to control the SFINAE `using has_extra_types = std::true_type;` `using foo_t = type1;` `using foo2_t = type2;` – xerion May 27 '15 at 13:02
0

You may create your traits:

namespace detail
{
    template <typename T>
    decltype(std::declval<typename T::foo_t>(), std::true_type{}) has_foo_t_impl(int);

    template <typename T>
    std::false_type has_foo_t_impl(...);
}

template <typename T>
using has_foo_t = decltype(detail::has_foo_t_impl<T>(0));

Then create an helper class:

template<typename Model, bool = has_foo_t<Model>>
struct BaseModelOptimizeHelper;

template<typename Model>
struct BaseModelOptimizeHelper<Model, true>
{
    void optimize_impl(const Model& model, const foo1& f1, const foo2& f2, foo3* f3, foo_t* f4){
        model.optimize(f1,f2,f3, f4);
    }
};

template<typename Model>
struct BaseModelOptimizeHelper<Model, false>
{
    void optimize_impl(const Model& model, const foo1& f1, const foo2& f2, foo3* f3)
    {
        model.optimize(f1,f2,f3);
    }
};

And finally:

template<typename Model>
class BaseModel : protected BaseModelOptimizeHelper<Model>
{
    const Model& impl() const { return static_cast<Model const&>(*this); }
public:
    template <typename ... Ts>
    void optimize(Ts&&... ts) { optimize_impl(impl(), std::forward<Ts>(ts)...); };

};
Jarod42
  • 203,559
  • 14
  • 181
  • 302