6

In a class Foo I have the following template functions :

class Foo
{
  public:
    template <typename T>
    static typename boost::enable_if<boost::is_abstract<T>, T*>::type allocate();
    template <typename T>
    static typename boost::disable_if<boost::is_abstract<T>, T*>::type allocate();
};

There is two declarations, but, for the user, only one function.

What is the usual way to document this kind of declaration with Doxygen ?

Idea #1 :

class Foo
{
  public:
    /** \brief This function throws a runtime error */
    template <typename T>
    static typename boost::enable_if<boost::is_abstract<T>, T*>::type allocate();
    /** \brief This function allocates an instance of the given type */
    template <typename T>
    static typename boost::disable_if<boost::is_abstract<T>, T*>::type allocate();
};

Idea #2 :

class Foo
{
  public:
    /** \brief This function allocates an instance of the given type if not abstract, throws an exception instead */
    template <typename T>
    static typename boost::enable_if<boost::is_abstract<T>, T*>::type allocate();
    template <typename T>
    static typename boost::disable_if<boost::is_abstract<T>, T*>::type allocate();
};
5
  • No chance to document template stuff in general with doxygen. Any kind of specialization is not handled in a expected fashion. Have you found a way to comment on template parameters at all? See also: stackoverflow.com/questions/3435225/… Commented Apr 13, 2016 at 12:11
  • @Klaus : For the moment, I have no other problem to document template functions with Doxygen. Maybe I didn't do complex things with templates before today... Commented Apr 13, 2016 at 12:18
  • For your example, instead of two overloads, you could just have one and then do BOOST_STATIC_ASSERT_MSG(!boost::is_abstract<T>::value, "T must be concrete") in it. This will generate a compile error if the requirements for T are not met, which is preferable to a runtime exception. The C++11 equivalent would be static_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"). Commented May 15, 2022 at 3:13
  • @EmileCormier actually, I need a runtime check in my context because the type is only known at runtime and new T must be called by allocate if 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. Commented May 16, 2022 at 7:38
  • @Caduchon Ah, now I understand. You could use tagged dispatching in that case. See the bottom part of my updated answer. Commented May 16, 2022 at 17:57

1 Answer 1

1

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;
      }
    }
};
Sign up to request clarification or add additional context in comments.

5 Comments

This is an interesting solution to the problem, though admittedly I'd absolutely hate to be in a codebase where people are wrapping otherwise visible/idiomatic uses of enable_if inside of macros, purely just so a third-party tool like doxygen can generate something decent. IMO code shouldn't be written strangely just to assist a tool.
You should also mention the version of doxygen you used.
@albert The Doxygen config options I used in my answer have been supported for over a decade.
@Human-Compiler This whole SFINAE business already is some strange-looking code to begin with. Hopefully C++20 constraints will improve things when it gains better support and adoption.
@EmileCormier it might be that the settings have been supported for over a decade but the underlying logic might have been wrong and has been corrected, so it is always good to mention the used version.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.