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

江枫思渺然 提交于 2019-12-06 04:52:15

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!

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*;
    // ...
};

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.

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