问题
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 diff
s 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