Typesetting: the east vs west pointless holy war?

In [1]:
#include "cleantype_examples_utils.h"
#include "types_variations.hpp"
#include <fplus/fplus.hpp>
using namespace types_variations;

In my opinion, the "east-const" vs "west-const" debate is just the tip of the iceberg. It is a sign that we would need additional "spelling conventions" as far as the types are concerned. Those rules might not be required for a successful compilation, but they should exist nonetheless.

In written english, the spelling rules say for example that each comma (",") should be followed by exactly one space (etc.), as well as each question mark ("?"), etc.

it is still possible to Write a sentence    ,and to ignore totally those rules    ;but it is considered Ugly -and rightfuly so -   !

Below are some examples:

Spacing rules

I do not think there is an established convention about the spacing of the types (apart from the fact the C++ Core Guideline advise to uses spaces sparingly). An established convention (even if not enforced by the compiler) would be welcome.

How many possible spellings for vector<map<char **, int>>? There is a whopping number of possibilities: 1024!

In [2]:
{
    std::string type1 = "vector<map<char**,int>>";
    auto l = space_variations(type1);
    std::cout << show_type_string_list(l);
}
---------------------------------------------------------------------------------
  "vector < map < char * * ,int > > " - 1024 Variations  
	10 samples out of 1024
---------------------------------------------------------------------------------
"vector < map < char * * ,int > > "
"vector<map < char * * ,int>> "
"vector< map <char * *,int> > "
"vector <map< char ** ,int >> "
"vector < map<char **,int > > "
"vector<map<char **,int>> "
"vector<map < char* * ,int> > "
"vector< map <char* *,int >> "
"vector <map< char** ,int > > "
"vector < map< char**,int>> "

East vs west

Below is the cursed const T * const vs T const * const example.

My personal preference goes to const int * const (i.e spaces everywhere, and the "const" keyword is kept close to what is const).

However, who cares about my personal preference?
I do not even care that much!
I would strongly prefer to have an established convention to which I could refer.

In [3]:
{
    auto f = combine_transforms(add_const_ptr_const, space_variations);
    std::cout << show_type_string_list(f("int"));
}
--------------------------------------
  "const int * const" - 8 Variations  
--------------------------------------
"const int * const"
"const int *const"
"const int* const"
"const int*const"
"int const * const"
"int const *const"
"int const* const"
"int const*const"

To struct or not to struct

In C++, the struct (or class) keywords can be ellided.

However, MSVC chose to not ellide them in its typeid implementation, so that for const Foo & we have the following possibilities:

In [4]:
{
    auto f = combine_transforms(
        compiler_maybe_add_struct, add_const_ref, space_variations);
    std::cout << show_type_string_list(f("Foo"));
}
--------------------------------------------------------
  "const Foo & " - 16 Variations  
	10 samples out of 16
--------------------------------------------------------
"const Foo & "
"const Foo &"
"const Foo& "
"const Foo&"
"Foo const & "
"Foo const &"
"Foo const& "
"Foo const&"
"const struct Foo & "
"const struct Foo &"

On compiler and libc conventions

Compilers and libc implementations add their dose of complexity to the mix, since they add synonyms to the std namespace (std::__1:: or std::__cxx11::) and Visual Studio can modify pointer types by adding __ptr64 or __ptr32, whenever the compilation is done in 64 bits.

Let's examine the possible spellings of std::vector<const char *> by different compilers and/or different libc implementations:

In [5]:
{
    auto f = combine_transforms(
        compiler_maybe_add_ptr64, add_const, add_vector, compiler_maybe_add_std_namespace);
    std::cout << show_type_string_list(f("char"));
}
--------------------------------------------------------------------
  "std::vector<const char*>" - 32 Variations  
	10 samples out of 32
--------------------------------------------------------------------
"std::vector<const char*>"
"std::vector<const char*>"
"std::__1::vector<const char*, std::__1::allocator<const char*>>"
"std::__cxx11::vector<char* const>"
"std::vector<char* const, std::allocator<char* const>>"
"std::vector<char* const, std::allocator<char* const>>"
"std::__1::vector<const char* __ptr64>"
"std::__cxx11::vector<const char* __ptr64, std::__cxx11::allocator<const char* __ptr64>>"
"std::vector<char* __ptr64 const>"
"std::vector<char* __ptr64 const>"

Types indentation

When types become more complex, an indentation quickly becomes needed. An established convention on how to indent them would also be worthwile. Subjects like "when to start indenting" (after 2 or 3 nested levels for example), and how to indent are interesting subjects to be adressed.

One possible way to indent type could state that

join_view<transform_view<iota_view<int, void>, single_view<int> (*)(int)>, void> &

should be indented like this:

join_view<
    transform_view<
        iota_view<
            int,
            void
        >,
        single_view<
            int
        > (*)(int)
    >,
    void
> &

This indentation style is the default indentation style provided by CleanType

Note on const / volatile / noexcept qualifiers

All the previous examples mentioned types spellings that are exact synonyms, i.e they denote exactly the same type.

However in C++, there are also close synomyms with the afore mentioned qualifiers. They are a different subject, and for example the Boost CallableTraits manual gives a complete list of the possible variations for callable functions.