Travis |
Windows |
License |
Try this online!
|
cleantype
is a small header only library which offer readable type names, with a consistent naming scheme accross compilers, at run-time and compile-time. It can also output the signature of lambda functions.
The included tool ct_compiler_decipher
simplifies the template noise in your compiler output : just "|" (pipe)
your build tool to it.
It can be seeen as a developper friendly typeid
alternative, and as a tool for those who are tired by the template noise in the compiler output.
In C++, typeid.name() is able to display the type of variables.
However it has several limitations: const
, volatile
, &&
qualifiers are ignored; it cannot identify the signature of lambdas function, and last but not least, the returned name if often unreadable : for example std::set<std::string>
becomes
std::set<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >
This library tries to overcomes some of these limitations. It is composed mainly of C++11 / C++14 functions. It also contains some macros in order to be able to display rvalue reference type, as well as variables names. Macros are prepended with a suffix 'm_'.
The returned types names should be similar accross compilers.
Note: this library is heavily tested, with clang, gcc and msvc. However, it should be considered alpha state.
cleantype
is a small header only library, so you just need to clone it and add it to your path.
Then, include cleantype/cleantype.hpp (this file includes a comprehensive API doc)
ct_compiler_decipher
is comprised of a single c++ file. It's compilation can be done viamake
or via$(CXX) -Isrc/include -Ithird_party/FunctionalPlus/include --std=c++14 src/tools/ct_compiler_decipher/ct_compiler_decipher.cpp -o ct_compiler_decipher
This manual is written using cling, xeus cling and jupyter notebook. Cling enables a Read-Eval-Print-Loop (REPL) development mode with C++. This approach benefits a lot from having a good type introspection, which is the aim of this library.
The code that you read in this manual is real live code that can be executed inside jupyter notebook.
You can try it directly inside binder : click on the "launch binder" at the top of this page.
Notes:
- Beware, it require about 2 minutes to load; but then you will be able to run the code live from your browser!
- Inside the notebook, click on the "Run" button in order to execute each cell (in order)
- You can modify and run the code as you desire inside binder!
- Note that there is a limitation in cling that requires that you add two ";" after each lambda function definition
The "#pragma cling add_include_path" is specific to cling. Beside this, everything is standard C++.
// Special macro to change the include path (this is required for the binder demo)
#pragma cling add_include_path("./include")
#include <cleantype/cleantype.hpp>
// The includes below are not required, they are just used for the purpose of this manual
#include <future>
#include <numeric>
#include <fplus/fplus.hpp>
// This macro is used later in this manual in order to display commands and their results
#define run_show(cmd) \
{ \
std::cout << #cmd << "\n"; \
std::cout << cmd << "\n\n"; \
}
-
cleantype::clean<T...>()
is a function that will return a string containing a readable type, for a given type or pack of types Use it with "cleantype::clean<decltype(var)>()"
Note: It will add a reference by default so that "int v = 5; cleantype::clean(v)" will return "int&". Use the macro CT_cleantype_clean() if you want to avoid this -
cleantype::clean<T...>(t...)
is a an easier version, using an instance of the type.
Notes:- It will add a reference. Use the macro CT_cleantype_clean() if you want to avoid this
- It is not able to output correctly r-value references. For this, use
cleantype::clean<decltype(var)>()
-
cleantype::show_details(T && v)
is a function that will return a string containing the readable type of a variable, as well as its content -
CT_cleantype_clean(var)
is a macro that will also return the full type, but, it is able to also correctly display rvalue reference types. -
CT_show_details(var)
is a macro that will return a string containing the name, type and content of a variable (in this case, the underlying type of 'var' has to have an 'ostream & operator<<') -
CT_show_details_cont
(macro) is a version of CT_show_details for complex containers like "std::map". "cont" stands for "container".
// Lets define a function with an auto return type : what is its return type?
auto my_range(int nb)
{
std::list<int> l(nb);
std::iota(l.begin(), l.end(), 1);
return l;
}
auto v = my_range(5);
run_show( cleantype::clean(v) )
run_show( cleantype::clean<decltype(v)>() )
run_show( cleantype::show_details(v) )
run_show( CT_cleantype_clean(v) )
run_show( CT_show_details(v) )
cleantype::clean(v)
std::list<int> &
cleantype::clean<decltype(v)>()
std::list<int>
cleantype::show_details(v)
std::list<int> & = [1, 2, 3, 4, 5]
CT_cleantype_clean(v)
std::list<int>
CT_show_details(v)
[std::list<int>] v = [1, 2, 3, 4, 5]
Below, an where a function receives value by r-value references and can display its type & value correctly.
{
auto log_received = [](auto && v) {
std::cout << cleantype::full<decltype(v)>() << std::endl;
std::cout << CT_show_details(v) << "\n";
};
log_received(42);
}
int &&
[int &&] v = 42
std::cout << cleantype::clean(1, "Hello") << std::endl;
std::cout << cleantype::clean<std::string, int, int &&, char &&>() << std::endl;
int, char const( &)[6]
std::string, int, int &&, char &&
You can customize the suppressions and replacements inside cleantype/cleantype_configuration.hpp
-
cleantype::full<T...>()
is a function that will return a string containing the full type. It also works with packs of types. Use it with "cleantype::full<decltype(var)>()"
It will add a reference by default so that "int v = 5; cleantype::full(v)" will return "int&". Use the macro CT_cleantype_full() if you want to avoid this -
cleantype::full<T...>(t...)
is a an easier version, using an instance of the type.
Notes:- It will add a reference by default so that int v = 5; cleantype::full(v) will return "int&" => use the macro CT_cleantype_full() if you want to avoid this
- It is not able to output correctly r-value references
For this, use
cleantype::full<decltype(var)>()
-
cleantype::show_details_full(T && v)
is a function that will return a string containing the full type of a variable, as well as its content -
CT_cleantype_full(var)
is a macro that will also return the full type, but, it is able to also correctly display rvalue reference types. -
CT_show_details_full(var)
is a macro that will return a string containing the name, type and content of a variable (in this case, the underlying type of 'var' has to have an 'ostream & operator<<') -
CT_show_details_full_cont
is a version of CT_show_details_full for complex containers like "std::map". "cont" stands for "container".
auto w = my_range(10);
run_show( cleantype::full(w) )
run_show( cleantype::full<decltype(w)>() )
run_show( cleantype::show_details_full(w) )
run_show( CT_cleantype_full(w) )
run_show( CT_show_details_full(w) )
cleantype::full(w)
std::__cxx11::list<int, std::allocator<int> > &
cleantype::full<decltype(w)>()
std::__cxx11::list<int, std::allocator<int> >
cleantype::show_details_full(w)
[std::__cxx11::list<int, std::allocator<int> > &] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
CT_cleantype_full(w)
std::__cxx11::list<int, std::allocator<int> >
CT_show_details_full(w)
[std::__cxx11::list<int, std::allocator<int> >] w = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Full types can quickly become unreadable, especially with templated types, such as in the STL.
std::set<std::string> my_set { "Hello", "There"};
run_show( cleantype::show_details_full(my_set) )
run_show( cleantype::show_details(my_set) )
cleantype::show_details_full(my_set)
[std::set<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > &] = [Hello, There]
cleantype::show_details(my_set)
std::set<std::string> & = [Hello, There]
CT_show_details_cont
(macro) is a version of CT_show_details for complex containers like "std::map". "cont" stands for "container".CT_show_details_full_cont
enables to display the full type and content
These version are required for certains more complex containers, like "std::map". "cont" stands for "container".
std::map<std::string, int> my_map {{{"a", 1}, {"b", 2}, {"c", 3} }};
run_show( CT_show_details_cont(my_map) )
run_show( CT_show_details_full_cont(my_map) )
CT_show_details_cont(my_map)
[std::map<std::string, int>] my_map = [(a, 1), (b, 2), (c, 3)]
CT_show_details_full_cont(my_map)
[std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> > >] my_map = [(a, 1), (b, 2), (c, 3)]
ct_compiler_decipher
is a tool that deciphers the compiler output and makes it more readable, especially when there are lots of templatesCT_compiler_log_type(T)
is a macro that will create an intentional compiler error whose intent is to display the type name of T. You can use it in conjunction with "ct_compiler_decipher".CT_compiler_log_var_type
is a macro that will create an intentional compiler error whose intent is to display the type name of the variable var. You can use it in conjunction with "ct_compiler_decipher".
First let's build ct_compiler_decipher : it is composed of unique cpp file, so that it's compilation is extremely easy
#include "build_ct_tools.cpp"
make_ct_compiler_decipher();
make 2>&1
g++ -Isrc/include -Ithird_party/FunctionalPlus/include --std=c++14 src/tools/ct_compiler_decipher/ct_compiler_decipher.cpp -o ct_compiler_decipher
The code below is intentionally bad, in order to get the compiler to spit incomprehensible template errors. Do not even try to make sense out of it :-)
std::string code =
R"CODE(
#include <fplus/fplus.hpp>
#include <string>
#include <map>
#include <vector>
auto my_spurious_lambda = [](int a, int b) {
std::map<std::string, int> r1;
std::vector<decltype(r1)> r2;
for (std::size_t i = 0; i < b; i++) r2.push_back(r1);
auto add_one = [](auto x) { return x + 1; };
auto r3 = fplus::transform(add_one, r2);
return r3;
};
int main() { auto v = my_spurious_lambda(1, 3); }
)CODE";
Let's compile it with the default compiler. Below, we only show the begining of the compiler output (it is followed by around 90 similar lines).
compile_code__extract(code);
clang++ --std=c++14 -c code.cpp -Iinclude -o a.out 2>&1 | head -3 2>&1
code.cpp:10:42: error: invalid operands to binary expression ('std::map<std::__cxx11::basic_string<char>, int, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, int> > >' and 'int')
auto add_one = [](auto x) { return x + 1; };
~ ^ ~
Let's commpile it and pipe the compiler output to ct_compiler_decipher
:
clang++ --std=c++14 -c code.cpp -Iinclude -o a.out 2>&1 | ct_compiler_decipher
compile_code_decipher__extract(code);
clang++ --std=c++14 -c code.cpp -Iinclude -o a.out 2>&1 | ct_compiler_decipher | head -3 2>&1
code.cpp:10:42: error: invalid operands to binary expression ('std::map<std::string, int> ' and 'int')
auto add_one = [](auto x) { return x + 1; };
~ ^ ~
Sometimes it is easier to be able to identify a type at compile time. This is especially true, if the code in question is run long after the application start.
In this case, the macros CT_compiler_log_type(T)
and CT_compiler_log_var_type
come handy. They will create an intentional compiler error whose intent is to display the type name of the variable var. You can use them in conjunction with "ct_compiler_decipher".
See an example below:
std::string code2 =
R"CODE(
#include <cleantype/cleantype.hpp>
#include <fplus/fplus.hpp>
auto mystery_lambda = [](int end) {
return fplus::overlapping_pairs_cyclic( fplus::numbers(0, end) );
};
int main() {
auto v = mystery_lambda(10);
CT_compiler_log_var_type(v); // Here we ask the compiler to give us the type of v
}
)CODE";
compile_code_decipher__extract(code2);
clang++ --std=c++14 -c code.cpp -Iinclude -o a.out 2>&1 | ct_compiler_decipher | head -3 2>&1
code.cpp:10:5: error: no member named 'IntentionalError' in 'std::vector<std::pair<int, int>> '
CT_compiler_log_var_type(v); // Here we ask the compiler to give us the type of v
^ ~
cleantype::full_compiletime<T>()
will give you the full name of a type in the form of a Boost.Hana string.
Note: For this, you need to manually include "cleantype/cleantype_compiler_typename.hpp", and to have boost in your include path. The rest of the library (including ct_compiler_decipherer) can be compiled in a standalone way.
The basic idea of having compile time type name is to use __PRETTY_FUNCTION__
as a way to get the type name in a constexpr way. The original idea comes from the ctti library, and is also used inside Boost.Hana's experimental/type_name.hpp.
This project goes beyond what was done in these projects, by adding a support for gcc and msvc (only clang was supported originaly).
Based on the work done during the development of this librayr, a Pull Request was posted to Boost.Hana. It proposes to include the support of MSVC and GCC for boost/hana/experimental/type_name.hpp.
-
cleantype::lambda_clean<LambdaFn>(LambdaFn fn)
is a function that will return a string containing the readable signature of a non generic lambda -
cleantype::lambda_clean<LambdaFn>(LambdaFn fn)
is a function that will return a string containing the full signature of a non generic lambda -
CT_show_details_lambda(var)
is a macro that will return a string containing the readable signature of a lambda and its name -
CT_show_details_lambda_full(var)
is a macro that will return a string containing the full signature of a lambda and its name
It is not alway easy to guess the return type of lambda. See the lambda below for example : it's return type is not easy to guess:
int start = 5;
// what is the return type of this lambda ?
auto mystery_lambda = [&start](int end) {
return fplus::overlapping_pairs_cyclic( fplus::numbers(start, end) );
};;
// Let's see
run_show( cleantype::lambda_clean(mystery_lambda) );
run_show( CT_show_details_lambda(mystery_lambda) );
cleantype::lambda_clean(mystery_lambda)
lambda: (int) -> std::vector<std::pair<int, int>>
CT_show_details_lambda(mystery_lambda)
[lambda: (int) -> std::vector<std::pair<int, int>>] mystery_lambda
If we try to get the type of this lambda via CT_cleantype_full
, we do not get much information...
std::cout << cleantype::full<decltype(mystery_lambda)>();
(lambda at input_line_26:4:23)
This is because "mystery_lambda" is actually a instance of a hidden class. We are actually looking for the signature of the operator() of this class. type_lambda_clean
is able to extract the type of this operator and to display it in a readable way.
cleantype::lambda_generic_clean<Args...>(LambdaFn fn)
is a function that will return a string containing the readable signature of a generic lambda. You will need to specify the type of the auto parameterscleantype::lambda_generic_full<Args...>(LambdaFn fn)
is a function that will return a string containing the full signature of a generic lambda.CT_type_lambda_generic_fromparams_XXX(lambda, arg1, arg2, ...)
is a macro that will return a string containing the signature of a generic lambda where you do not specify the args type, instead you give example of these types. (XXX is the number of params of the lambda, and can vary from 1 to 5).
Note: this is an advanced feature, and it might fail on certain compilers, such as gcc (in which case, you will get no output).
auto add = [](auto a, auto b) {
return a + b;
};;
This lambda ("add") is a generic lambda (which means that at least one of its argument is specified with an "auto" type). It's behaviour is comparable to a template function. So, if we try to get the signature via "cleantype::lambda_clean" we will run into an error. The solution is to provide the types of the input parameters:
// extract the lambda type using actual types
std::cout << cleantype::lambda_generic_clean<int, double>(add) << std::endl;
lambda: (int, double) -> double
It can also be done by providing some example parameters : use CT_type_lambda_generic_fromparams_XXX
, where X is the number of parameters of the lambda.
// extract the lambda type using example params
std::cout << CT_type_lambda_generic_fromparams_2(add, 1u, -2);
lambda: (unsigned int, int) -> unsigned int
This second version is useful when you are lost in a forest of "auto" variables deep in the call stack, and you do not know the return type of the lambda, and you do not even know the type of the input parameters: in that case, if you have a working call example, then you can use it.
Work in progress...
auto auto_fonction(int b) {
std::vector<int> range = fplus::numbers(0, b);
return fplus::pairs_to_map_grouped( fplus::overlapping_pairs_cyclic(range) );
};
cleantype
handles quite well const
, volatile
, references (&
), rvalue references (&&
), and pointers (*
). See below a demonstration
auto foo = [](auto && x) {
run_show( CT_cleantype_clean(x) );
run_show( cleantype::clean<decltype(x)>() );
};;
#define test_call_foo(var, title) \
std::cout << " *********** " << title << " ***********\n"; \
std::cout << "Bare variable (before the call)\n"; \
run_show( cleantype::clean<decltype(var)>() );\
run_show( CT_cleantype_clean(var) );\
std::cout << "Now inside foo (pass by universal reference)\n"; \
foo(var);
{
int a = 5;
test_call_foo(a, "Calling with an int lvalue (transformed to a ref by the call)");
int const & b = a;
test_call_foo(b, "Calling with a const lvalue reference");
test_call_foo(42, "Calling with an rvalue reference");
int * const c = &a;
test_call_foo(c, "Calling with a const pointer");
int const * d = &a;
test_call_foo(d, "Calling with a pointer to const");
int volatile vol = 42;
test_call_foo(vol, "Calling with a volatile int");
int const e = 5;
test_call_foo(e, "Calling with a const");
}
*********** Calling with an int lvalue (transformed to a ref by the call) ***********
Bare variable (before the call)
cleantype::clean<decltype(a)>()
int
CT_cleantype_clean(a)
int
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
int &
cleantype::clean<decltype(x)>()
int &
*********** Calling with a const lvalue reference ***********
Bare variable (before the call)
cleantype::clean<decltype(b)>()
const int &
CT_cleantype_clean(b)
const int &
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
const int &
cleantype::clean<decltype(x)>()
const int &
*********** Calling with an rvalue reference ***********
Bare variable (before the call)
cleantype::clean<decltype(42)>()
int
CT_cleantype_clean(42)
int
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
int &&
cleantype::clean<decltype(x)>()
int &&
*********** Calling with a const pointer ***********
Bare variable (before the call)
cleantype::clean<decltype(c)>()
int * const
CT_cleantype_clean(c)
int * const
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
int * const &
cleantype::clean<decltype(x)>()
int * const &
*********** Calling with a pointer to const ***********
Bare variable (before the call)
cleantype::clean<decltype(d)>()
const int *
CT_cleantype_clean(d)
const int *
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
const int * &
cleantype::clean<decltype(x)>()
const int * &
*********** Calling with a volatile int ***********
Bare variable (before the call)
cleantype::clean<decltype(vol)>()
volatile int
CT_cleantype_clean(vol)
volatile int
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
volatile int &
cleantype::clean<decltype(x)>()
volatile int &
*********** Calling with a const ***********
Bare variable (before the call)
cleantype::clean<decltype(e)>()
const int
CT_cleantype_clean(e)
const int
Now inside foo (pass by universal reference)
CT_cleantype_clean(x)
const int &
cleantype::clean<decltype(x)>()
const int &