I'll answer this in terms of std::enable_if, but it should work just as well with boost::enable_if.
You can use macros that wrap enable_if and define them differently whether Doxygen is running or not.
In your Doxyfile, add the following to your configuration:
PREDEFINED = GENERATING_DOXYGEN
EXPAND_AS_DEFINED = ENABLE_IF \
ENABLED_TYPE
In your C++ project, define the following macros:
#ifdef GENERATING_DOXYGEN
#define ENABLE_IF(cond)
#define ENABLED_TYPE(T, cond) T
#else
#define ENABLE_IF(cond) typename std::enable_if<(cond)>::type
#define ENABLED_TYPE(T, cond) typename std::enable_if<(cond), T>::type
#endif
You may then use the macros as follows:
class Foo
{
public:
/** \brief Throws a runtime error
\tparam T An abstract type
\detail Only participates in overload resolution
when std::is_abstract<T>::value==true */
template <typename T>
static ENABLED_TYPE(T*, std::is_abstract<T>::value) allocate();
/** \brief Allocates an instance of the given type
\tparam T A concrete type
\detail Only participates in overload resolution
when std::is_abstract<T>::value==false */
template <typename T>
static ENABLED_TYPE(T*, !std::is_abstract<T>::value) allocate();
};
The ENABLED_TYPE macros will expand to the undecorated return type (in your case, T*) when Doxygen is running. When compiling, the macros will expand to std::enable_if<cond, T>::type to perform SFINAE.
Instead of ENABLE_IF, you may want to name the macro REQUIRES to make your code a bit more readable. You may also want to prefix your macros with the name of your project to avoid clashing with macros defined in other libraries or in whatever super-project may want to consume your project.
What you can also do is have a single public function, and then use tag dispatching for your two cases:
class Foo
{
public:
/** \brief Allocates an instance of the given type
\tparam T Must be a concrete type (checked at run time) */
template <typename T>
static T* allocate()
{
do_allocate(std::is_abstract<T>());
// ...
}
private:
template <typename T>
T* do_allocate(std::false_type)
{
return new T;
}
template <typename T>
T* do_allocate(std::true_type)
{
throw std::runtime_error("T must be concrete");
}
};
With C++17, you could simply do if constexpr:
class Foo
{
public:
/** \brief Allocates an instance of the given type
\tparam T Must be a concrete type (checked at run time) */
template <typename T>
static T* allocate()
{
if constexpr (std::is_abstract<T>())
{
throw std::runtime_error("T must be concrete");
}
else
{
return new T;
}
}
};
BOOST_STATIC_ASSERT_MSG(!boost::is_abstract<T>::value, "T must be concrete")in it. This will generate a compile error if the requirements forTare not met, which is preferable to a runtime exception. The C++11 equivalent would bestatic_assert(!std::is_abstract<T>::value, "T must be concrete"), and in C++17:static_assert(!std::is_abstract_v<T>, "T must be concrete").new Tmust be called byallocateif not abstract. During the execution, the other function will never be called (some check before), but the compiler doesn't know it and want to write the branch.