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;
}
}
};