自我记录类型别名(typedef)以指示它将在另一个特定类中使用

时间:2017-05-22 09:55:50

标签: c++ templates documentation c++14 typedef

如何自我记录在另一个特定库中使用的类型别名?

在下面的示例中,类User定义了一个别名User::type,该别名应该仅通过Library在类T::type中引用。

这是图表: -

enter image description here

Library.h

Library<T>仅预期T定义某个别名(例如,此示例中为T::type)。

#include <iostream>
class Base{};     //dummy for the sake of example
template<class T>class Library{
    Base* t=nullptr;
    public: typename T::type getValue(){return static_cast<typename T::type>(t);}
    //some complex function, e.g. T::aType::doSomething()
};

在实际情况中,Library<T>预期多个别名,例如T::aTypeT::bTypeT::callbackType

User.h

要使用上述库,必须定义::type,例如如下: -

class Derived : public Base{};  //dummy for the sake of example
class User{
    public: using type=Derived*;//<-- poorly documented
    //... other alias e.g. aType=int*, bType=SomeClass*
    //... other complex functions
};

以下是用法( full demo ): -

int main(){
     Library<User> lib;
     lib.getValue();
     std::cout<<"OK"<<std::endl;
}

问题

请注意User::type确实缺乏自我记录 在现实生活中,大多数程序员 - 包括设计它的人 - 忘记了User::type的用途。

User::type未在User.h内部引用,因此一些编码人员可以随意删除它。

我觉得我们心爱的代码是从内部腐烂的,我想到了保存它的方法。

问题

如何自我记录类型别名以指示名称的方式/位置?

我糟糕的解决方案

1。评论

 class User{
    /** It is used for Library.h */
    public: using type=Derived*;

它变得非常快,我仍然更喜欢使用C ++ - 语义而不是随机评论。

2。使类型名称更具描述性

 class User{
    /** It is used for Library.h */
    public: using LIBRARY_type=Derived*;

非常混乱。

注意:此问题与How to self-document a callback function that is called by template library class?类似,但此问题约为type-def,而该问题约为callback

3 个答案:

答案 0 :(得分:2)

这里你唯一真正的问题似乎是“某些程序员随机删除它是一个容易攻击的目标”。

解决这个问题的方法不是要考虑自我文档和名称,而是要进行同行评审回归测试。为什么团队中的程序员“随意删除”并侥幸逃脱?这需要停止。

您可以通过简单的代码注释来保存必须回滚此类更改的管理难题:

/**
 * Provided for use by Library.
 */
using type=Derived*;

就是这样。这就是你所需要的。它丝毫不是“脏” - 它告诉其他编码器为什么存在type声明,如果有人删除它,它会像diff中的一个痛苦的拇指一样突出。然后你可以问他们,“你是如何得出结论,图书馆不再需要这个声明,为什么要删除它值得破坏我们的API?”

简而言之,这只是一个人为问题。标准的成员类型库中有很多名为type的例子,所以从技术角度来看,你已经在做你应该做的事了。不要试图反映您的类型的预期用途的名称;使名称描述的内容。 C ++委员会用std::move犯了同样的错误!

答案 1 :(得分:1)

您可以为此创建一个类型特征:

template <typename T> struct library_trait; // No definition
// Need to define library_trait<T>::type for ...
// library_trait<T>::atype for ...

在课程Library中,使用library_trait<T>::type代替typename T::Type

在使用Library<User>之前的位置(如示例中的main所示):

library_trait专门User

template <> struct library_trait<User>
{
    using type = Derived*;
    // ...
};

答案 2 :(得分:1)

特质类是此问题的常用解决方案。它很尴尬,因为你必须在它自己的命名空间内专门化它。

另一种方法是创建一个特质函数。

namespace utility {
  template<class T>struct tag_t{using type=T; constexpr tag_t(){}};
  template<class T>constexpr tag_t<T> tag{};
  template<class Tag> using type_t=typename Tag::type;
}
namespace MyLibrary {
  template<class T>
  void library_trait_name_f(tag_t<T>,...);
  template<class T>using library_trait_name=type_t<decltype(library_trait_name_f(tag<T>))>;
}

现在支持你:

namespace Elsewhere {
  struct Foo;
}

你可以在任何地方写:

namespace Elsewhere { // or MyLibrary, or even utility
  tag_t<int> library_trait_name_f(tag_t<Foo>);
}

如果可见,它将被MyLibrary::library_trait_name<T>选中。特别是,MyLibrary::library_trait_name<Elsewhere::Foo>int

这样做的好处是它允许您在库和库中的类型之间,类型旁边或第三个位置编写thr绑定。缺点是缺乏范围和非常规性。

MSVC对基于decltype的SFINAE也表现不佳,因此在MSVC中使用上述SFINAE时需要小心。