【问题标题】:Hiding template implementation details from Doxygen从 Doxygen 隐藏模板实现细节
【发布时间】:2013-08-09 20:57:50
【问题描述】:

C++(非常多)不幸的设计缺陷之一是,在使用模板元编程时,基本上不可能将实现与接口分开。

我的图书馆里到处都是这样的东西:

template <typename Ma, typename Mb>
typename boost::enable_if_c<
            detail::IsMatrix<Ma>::val and detail::IsMatrix<Mb>::val and
            detail::MatrixDimensionCheck<Ma,Mb>::isStaticMatch, 
        bool>::type
operator==(const Ma &a, const Mb &b) {
    return detail::matrixEqual(a,b);
}

如果这不可读,我不怪你。如果参数是矩阵和匹配维度,则大部分混乱只是将返回类型定义为bool,如果它们是其他东西,则定义为未定义(因此依靠 SFINAE 来防止此运算符隐藏其他重要的东西)。

由于本质上是静态类型检查函数的内容现在嵌入到我的普通 C++ 函数的签名中,这些实现内容将出现在生成的文档中。

我不希望用户必须阅读此内容。他们只需要知道这个函数返回一个bool(这几乎不可能通过阅读上面的内容来判断)。在文档中,我可以用简单的英语简洁地解释,这个运算符只接受矩阵。

有没有办法说服 Doxygen 将这种类型的混乱渲染为bool? (我假设或多或少没有办法直接在代码中清理它,但如果你能想到一些想法,欢迎提出想法)。

【问题讨论】:

  • 我更喜欢手写文档而不是“自动生成”文档是有原因的。我几乎总是花更多的时间来设置文档系统/将其修复到可接受的范围内,同时我本可以已经以实际好的格式编写所有文档。
  • @nightcracker:当然,直到您更改任何内容,例如参数、函数等,而无需更新文档。然后它变得不同步并且变得比无用更糟糕。此外,Doxygen 也很好地支持手写文档。
  • 可能相关的是this问题。
  • Bjarne Stroupstrup 在他的 FAQ 中有一条评论:“像所有强大的技术一样,它们 [模板元编程] 很容易被过度使用” 别误会,我喜欢模板元编程。但它必须与常识一起使用。它只是一个建议,而不是对您的代码的批评。
  • 我会使用一个库的(非常麻烦,我同意)方法,它的实现严重依赖于这种不可隐藏的模板魔法,就是制作一个单独的头文件,其中只包含干净的 " 为if" 版本的接口并在该接口上运行 doxygen。当然,对于大型库,这可能会变得非常麻烦,但我已经为小型单文件仅标头库完成了此操作,并且效果很好。当然,您必须使两个文件保持同步,但是当无法直接从源文件生成文档时,情况总是如此。

标签: c++ templates doxygen


【解决方案1】:

怎么样:

#ifdef DOXYGEN
    #define RETURN_TYPE(Test, Type1) Type1
#else
    #define RETURN_TYPE(Test, Type1) typename boost::enable_if_c< Test, Type1 >::type
#endif

template <typename Ma, typename Mb>
RETURN_TYPE((detail::IsMatrix<Ma>::val 
        and detail::IsMatrix<Mb>::val 
        and detail::MatrixDimensionCheck<Ma,Mb>::isStaticMatch), bool) 
operator==(const Ma &a, const Mb &b) { return detail::matrixEqual(a,b); }

恕我直言,它比最初的 C++ 代码更容易阅读和理解。 请注意第一个宏参数中的双括号,以避免编译器在“测试”中中断逗号。如果您首先重新排序返回类型(Type1),则可以摆脱它,并使用变量 arg 宏进​​行测试。

【讨论】:

  • 我不喜欢强制更改代码的文档。
  • 通常我同意,但在这种情况下,宏实际上获得了可见性。使用宏比不使用宏时代码的含义更清楚。宏在 Doxygen 运行时会改变行为这一事实对代码有很好的好处(如果您不想污染代码,可以在 Doxygen 的脚本中完成)
【解决方案2】:

好吧,我可以实现这一点的唯一方法是复制函数定义而不是使用 doxygen 的自动功能,而是使用 @fn 命令。对于您的示例,类似

/*!@fn template <typename Ma, typename Mb> bool operator==(const Ma &a, const Mb &b)
 * @brief My equality operator
 * @note The operator is available if the types @c Ma and @c Mb match. 
 *       It will be discarded otherwise 
 */
 template <typename Ma, typename Mb>
   typename boost::enable_if_c<
     detail::IsMatrix<Ma>::val and detail::IsMatrix<Mb>::val and
     detail::MatrixDimensionCheck<Ma,Mb>::isStaticMatch, 
   bool>::type
 operator==(const Ma &a, const Mb &b) {
    return detail::matrixEqual(a,b);
 }

应该这样做。

【讨论】:

    【解决方案3】:

    重新说服 Doxygen 将 bool 显示为返回类型:我知道的唯一方法是 Raffi 的回答,并补充说您可能希望对 Doxygen 隐藏实际功能(有几种方法可以做到这一点)。

    重新清理:这可能看起来像

    template <typename Ma, typename Mb>
    typename bool_isEqual<Ma, Mb>::type 
    operator==(const Ma &a, const Mb &b)
    ...
    

    bool_isEqual 封装了所有模板类型逻辑,typedefs typebool 时足够了。 (之所以选择名称bool_isEqual,是因为假设有其他具有类似结构的模板函数返回bool,但有其他条件。)

    如果始终如一地这样做,它可能就足够可读了。

    【讨论】:

      【解决方案4】:

      我发现下面的方法很清楚:

      1. 在 Doxyfile 中添加 PREDEFINED = DOXYGEN
      2. 在你的源代码中用///@cond .... ///@endcond包围SFINAE函数
      3. #ifdef DOXYGEN 内的源代码中放置一个简单的模板函数声明,这样它对正常编译是不可见的。观察:

        ///@cond
        template <typename Ma, typename Mb>
        typename boost::enable_if_c<
            detail::IsMatrix<Ma>::val and detail::IsMatrix<Mb>::val and
            detail::MatrixDimensionCheck<Ma,Mb>::isStaticMatch, 
            bool>::type
        operator==(const Ma &a, const Mb &b) {
            return detail::matrixEqual(a,b);
        }
        ///@endcond
        #ifdef DOXYGEN
            ///Documentation for your function...
            template<typename Ma, typename> operator==(const Ma &a, const Mb &b);
        #endif
        

      【讨论】:

        【解决方案5】:

        我认为这可能对你有用。显然,这是一个比您的示例更简单的示例,但基本思想是使用没有 enable_if 的已记录模板函数来调用另一个未记录但提供 SFINAE 的“隐藏”函数。

        // Ignore this function in doxygen
        template <typename T>
        typename boost::enable_if<boost::is_unsigned<T>, bool>::type
        test_hidden(T t) {
            return true;
        }
        
        template <typename T>
        typename boost::disable_if<boost::is_unsigned<T>, bool>::type
        test_hidden(T t) {
            return false;
        }
        
        // Document this function
        template <typename T>
        bool test(T t)
        {
            return test_hidden(t);
        }
        
        int main()
        {
           unsigned int a = 1;
           int b = 0;
        
           std::cout << test(a) << std::endl; // true
           std::cout << test(b) << std::endl; // false
        
           return 0;
        }
        

        【讨论】:

        • SFINAE 仅适用于模板签名,不适用于模板主体,因此这不是等效的。如果模板体的扩展无效,编译器不会尝试其他模板;它只会出错并停止。
        • 我同意这种方法确实有一些限制。但是,在我的示例中,编译器将尝试 test_hidden 的不同实现,这在某些情况下可能就足够了。我已经更新了我的答案以显示这一点。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多