Mixing templates with std::enable_if_t and specialisation

Viewed 204

Edit: done a rewrite of the provided code from feedback to help illustrate the issue

Say I have a method template<T> do_foo(T) and that I use it purely using std::enable_if to define return values

This allows me to write signatures for it such as

// templates_top.h

template<typename T>
std::enable_if_t<std::is_fundamental<T>::value, int> do_foo(T bb)
{
    return 0;
}

I can also put in more definitions for do_foo further down the line - templates_top.h doesn't need to have fwd declarations for everything that could go into do_foo - what ever invokes do_foo just has to.

Now say I have a couple of classes for which the implementation of do_foo would ideally not be in a header

// templates_a.h
#include "./templates_top.h"
#include "./templates_b.h"

class LocalTypeA { 
  public:
    LocalTypeB* internal_;
};

template<typename T>
std::enable_if_t<std::is_same<T, LocalTypeA>::value, int> do_foo(T bb)
{
    // Some detailed business logic 
    // With recursive do_foo for member
    return do_foo<LocalTypeB>(*(bb.internal_));
}

// templates_b.h
#include "templates_top.h"

class LocalTypeB { 
    public: 
        bool the_val = false;
};

template<typename T>
std::enable_if_t<std::is_same<T, LocalTypeB>::value, int> do_foo(T bb)
{
    // Some detailed business logic here 
    // specific to LocalTypeB that ideally isn't in a header
    // Also not that do_foo is called recursively for the std::is_fundamental type
    return do_foo<bool>(bb.the_val);
}

All that works fine when its all in the header. But now - we want to move that complex business logic out of the headers.

We can't fully-specialise it like template<> do_foo(LocalTypeB) as there is no generic implementation template<typename T> do_foo(T). If we make that generic one - then the std::is_fundamental overload becomes ambiguous.

Is there a way for me to write this scenario so that

  • templates_top.h preserves its ignorance of LocalTypeA/B and its use of std::is_fundamental
  • Allowing the overloads for LocalTypeA/B to moved to their own respective impl files, and still resolve the std::enable_if signatured functions

FWIW - my target C++ standard is C++11, but I'm guessing features from C++14/17 may make this possible, so they are good too.

Worth stating - this is a fairly big simplification of a real problem that I've had some trouble explaining - I'm looking more for ways to solve the heart of the type conflict on a much larger scale.

Code sample here - compile as g++ main_a.cpp or g++ main_b.cpp

1 Answers

Functions cannot be partially specialized.

SFINAE can indeed be used to discard overload.

It seems that tag dispatching is more appropriate here:

template <typename T> struct Tag{};

template <typename T>
auto do_foo(Tag<bool>, T data)
{
    return true;
}

template <typename T>
void do_foo(Tag<void>, T data) = delete;

// ...

template <typename R, typename R>
auto do_foo(T data) -> decltype(do_foo(Tag<R>{}, data))
{
    return do_foo(Tag<R>{}, data);
}
Related