如何使用Doxygen记录“enable_if”函数

6
在一个名为Foo的类中,我有以下模板函数:
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();
};

有两个声明,但是对于用户来说只有一个函数。

在Doxygen中记录这种声明的通常方法是什么?

想法 #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();
};

点子 #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();
};

没有机会用Doxygen来记录模板的一般性内容。任何形式的特殊化都不能以预期的方式处理。你找到了一种注释模板参数的方法吗?另请参见:https://dev59.com/pnA75IYBdhLWcg3wK1wp?rq=1 - Klaus
@ Klaus:目前为止,我使用Doxygen没有遇到其他关于文档模板函数的问题。也许在今天之前我还没有对模板进行过复杂的操作... - Caduchon
对于你的例子,你可以只有一个重载函数,并在其中使用BOOST_STATIC_ASSERT_MSG(!boost::is_abstract<T>::value, "T must be concrete")。如果不满足T的要求,则会生成编译错误,这比运行时异常更可取。在C++11中的等效语句是static_assert(!std::is_abstract<T>::value, "T must be concrete"),而在C++17中则为static_assert(!std::is_abstract_v<T>, "T must be concrete") - Emile Cormier
@EmileCormier,实际上,在我的情况下,我需要一个运行时检查,因为类型只在运行时才知道,并且如果不是抽象的,则必须通过allocate调用new T。在执行过程中,其他函数永远不会被调用(在之前进行了一些检查),但编译器并不知道它,并想要编写该分支。 - Caduchon
@Caduchon 啊,现在我明白了。在这种情况下,你可以使用“标记式调度”。请参见我更新答案的底部部分。 - Emile Cormier
1个回答

1

我将使用std::enable_if来回答这个问题,但是同样可以使用boost::enable_if

您可以使用宏来包装enable_if,并根据Doxygen是否正在运行而定义不同的宏。

在您的Doxyfile中,将以下内容添加到配置中:

PREDEFINED = GENERATING_DOXYGEN

EXPAND_AS_DEFINED = ENABLE_IF \
                    ENABLED_TYPE

在您的C++项目中,定义以下宏:
#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

您可以按以下方式使用宏:
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();
};
ENABLED_TYPE宏在Doxygen运行时将扩展为未装饰的返回类型(在您的情况下为T*)。编译时,宏将扩展为std::enable_if<cond, T>::type以执行SFINAE。
您可以将宏命名为REQUIRES以使您的代码更易读。您还可以在宏前缀中添加项目名称,以避免与其他库或任何可能想要使用您的项目的超级项目中定义的宏发生冲突。
另外,您可以拥有一个公共函数,并使用tag dispatching来处理两种情况。
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");
    }
};

在C++17中,你可以简单地使用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;
      }
    }
};

这是一个有趣的解决方案,尽管我必须承认,我绝对讨厌在代码库中,人们会将本来可以看到/惯用的enable_if使用包装在宏内部,纯粹只是为了第三方工具(如doxygen)能够生成一些体面的东西。在我看来,代码不应该被奇怪地编写,只是为了帮助工具。 - Human-Compiler
你还应该提到你使用的doxygen版本。 - albert
@albert 我在回答中使用的Doxygen配置选项已经得到了十多年的支持。 - Emile Cormier
@Human-Compiler 整个SFINAE业务本来就是一些看起来很奇怪的代码。希望当它获得更好的支持和采用时,C++20约束将改善事物。 - Emile Cormier
@EmileCormier 可能是设置已经支持了十多年,但底层逻辑可能是错误的并已被更正,因此提及使用的版本总是很好的。 - albert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接