Self-document a type-alias (typedef) to indicate that it will be used in another certain class

不打扰是莪最后的温柔 提交于 2019-12-22 14:18:15

问题


How to self-document a type-alias that is used in another certain library?

In the below example, class User defines an alias User::type that is supposed to be referred only in class Library via T::type.

Here is the diagram :-

Library.h

Library<T> expected only T that defines a certain alias (e.g. T::type in this example).

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

In real cases, Library<T> expected many alias e.g. T::aType, T::bType, T::callbackType, etc.

User.h

To use the above library, ::type has to be defined e.g. as below :-

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

Here is the usage (full demo):-

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

Problem

Notice that User::type really lacks self-documentation.
In real life, most coders - including ones who designed it - forget what User::type is for.

User::type is not referred internally in User.h, so it is an easy target to be randomly deleted by some coders.

I feel that our beloved codes are rotten from inside, and I think about ways to save it.

Question

How to self document the type alias to indicate how/where it is called?

My poor solutions

1. comment

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

It gets dirty pretty fast, and I still prefer using C++-semantic rather than random comment.

2. make the type name more descriptive

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

It is quite messy.

Note: This question is similar to How to self-document a callback function that is called by template library class?, but this one is about type-def while that one is about callback.


回答1:


It seems that your only actual problem here is that "it is an easy target to be randomly deleted by some coders".

The solution to this is not to obsess over self-documentation and names, but to institute peer review and regression tests. Why are coders on your team "randomly deleting" things and getting away with it? That needs to stop.

You can probably save the administrative headache of having to rollback such a change, with a simple code comment:

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

That's it. That's all you need. It's not "dirty" in the slightest — it tells other coders why the type declaration exists, and will stand out like a sore thumb in diffs if anyone removes it. Then you can ask them, "how did you conclude that Library no longer requires this declaration, and why is removing it worth the breakage of our API?"

In short, this is only a human problem. There are plenty of examples in the standard library of member types called type, so from a technical standpoint you're already doing what you should. Don't try to shoe-horn in names that reflect your type's expected usage; make the name describe what it is. The C++ committee made the same mistake with std::move!




回答2:


You may create a type traits for that:

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

In class Library, use library_trait<T>::type instead of typename T::Type

In a place before the usage of Library<User> (as in main in your example):

Specialize library_trait for User.

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



回答3:


A trait class is the usual solution to this problem. It is awkward because you have to specialize it within its own namespace.

An alternative is to create a trait function.

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

now suppoae you have:

namespace Elsewhere {
  struct Foo;
}

you can write anywhere:

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

and if visible it will be picked up by MyLibrary::library_trait_name<T>. In particular, MyLibrary::library_trait_name<Elsewhere::Foo> is int.

The advantage of this is that it permits you to write thr binding between the library and the type in the library, next to the type, or in a 3rd location. The big disadvantages is the lack of scoping and the unconventionality of it.

MSVC also doesn't behave well with decltype based SFINAE, so using the above for SFINAE in MSVC you need be careful.



来源:https://stackoverflow.com/questions/44109929/self-document-a-type-alias-typedef-to-indicate-that-it-will-be-used-in-another

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!