1
\$\begingroup\$

I am building a P2P networking library that allows users to directly send instantiated objects. As such, I need a way to uniquely identify classes.

Reading through type_info documentation did not help me, as I only learnt they were implementation defined, and that type_info::hash_code "can also change between invocations of the same program."

I did a piece of code to do that, using the preprocessor. It works quite well but is extremely slow to compile. Do you have any ideas on how I could increase the compilation speed (note: my code must be C++14-compliant)?

#include <cstdint>
#include <string>
#include <utility>
#include <tuple>

#define BREEP_DECLARE_TYPE(T) \
    namespace breep { namespace detail { \
        template <> \
        struct networking_traits_impl<T> { \
            const std::string universal_name = std::string(#T); \
        }; \
    }}

#define BREEP_DECLARE_TEMPLATE(TType) \
    namespace breep { namespace detail { \
        template <typename... T> \
        struct networking_traits_impl<TType<T...>> { \
            networking_traits_impl(); \
            const std::string universal_name = std::string(#TType"<") + networking_traits_impl<typename std::tuple_element<0, std::tuple<T...>>::type>().universal_name + detail::identifier_from_tuple<detail::remove_type<0, T...>>().value + ">"; \
        }; \
        template <typename... T> \
        networking_traits_impl<TType<T...>>::networking_traits_impl() {}\
    }}

namespace breep {

    namespace detail {
        uint64_t hash(const std::string& str);

        template <typename>
        struct networking_traits_impl {};

        template<typename T>
        struct networking_traits_impl<T&> {
            const std::string universal_name = networking_traits_impl<T>().universal_name;
        };

        template<typename T>
        struct networking_traits_impl<T*> {
            const std::string universal_name = networking_traits_impl<T>().universal_name;
        };

        template<typename T>
        struct networking_traits_impl<const T> {
            const std::string universal_name = networking_traits_impl<T>().universal_name;
        };

        template<typename T>
        struct networking_traits_impl<T&&> {
            const std::string universal_name = networking_traits_impl<T>().universal_name;
        };

        template<typename T>
        struct networking_traits_impl<volatile T> {
            const std::string universal_name = networking_traits_impl<T>().universal_name;
        };
    }

    template <typename>
    struct universal_name {};

    template <typename T>
    struct type_traits {

        static const std::string& universal_name(){
            static const std::string name = detail::networking_traits_impl<T>().universal_name;
            return name;
        }

        static uint64_t hash_code() {
            static const uint64_t hash = detail::hash(universal_name());
            return hash;
        }
    };

    namespace detail {

        // sdbm's hash algorithm, gawk's implementation.
        uint64_t hash(const std::string& str) {
            uint64_t hash_code = 0;

            for (std::string::size_type i = str.size() ; i-- ;) {
                if (str[i] != '>' && str[i] != ',' && str[i] != ' ' && (str[i] != ':' || str[i+1] != ':')) {
                    hash_code = str[i] + (hash_code << 6) + (hash_code << 16) - hash_code;
                }
            }
            return hash_code;
        }

        template<std::size_t N, typename Tuple, std::size_t... Idx>
        auto remove (std::index_sequence<Idx...>) ->
        decltype(std::tuple_cat(std::declval<std::conditional_t<(N == Idx),
                std::tuple<>,
                std::tuple<typename std::tuple_element<Idx, Tuple>::type>>>()...
        ));

        template <std::size_t N, typename... Args>
        using remove_type = decltype(detail::remove<N,std::tuple<Args...>>(std::index_sequence_for<Args...>{}));

        template<typename... T>
        struct identifier_from_tuple {
        };

        template<>
        struct identifier_from_tuple<std::tuple<>> {
            static const std::string value;
        };
        const std::string identifier_from_tuple<std::tuple<>>::value = "";

        template<typename... T>
        struct identifier_from_tuple<std::tuple<T...>> {
            static const std::string value;
        };
        template<typename... T>
        const std::string identifier_from_tuple<std::tuple<T...>>::value =
                "," + networking_traits_impl<typename std::tuple_element<0, std::tuple<T...>>::type>().universal_name +
                identifier_from_tuple<remove_type<0, T...>>::value;
    }
}

If you want to see the full source, you may find it on GitHub.

\$\endgroup\$
1
\$\begingroup\$

Prefer Not to Use Macros
Generally #define and especially macros are frowned upon in C++.

  • Macros are very difficult to debug (By the comments in the GitHub repository you already know the most this important reason).
  • A second reason is that Macros tend to hide data, for instance both of the macros in the header file are hiding the fact that the templates are 2 namespace levels down.

To make it easier to use the code you might want to have comments at the top of the file that the user can copy and paste rather than having the macros.

/*
namespace breep {
    namespace detail {
        template <>
        struct networking_traits_impl<T> {
            const std::string universal_name = std::string(#T);
        };
    }
}
*/

/*
namespace breep {
    namespace detail {
        template <typename... T>
        struct networking_traits_impl<TType<T...>> {
            networking_traits_impl();
            const std::string universal_name = std::string(#TType"<") + networking_traits_impl<typename std::tuple_element<0, std::tuple<T...>>::type>().universal_name + detail::identifier_from_tuple<detail::remove_type<0, T...>>().value + ">"; 
        };
        template <typename... T>
        networking_traits_impl<TType<T...>>::networking_traits_impl() {}
    }
}
 */

Slow Compiling
Two possible reasons, one is that the macros are hiding the fact that there is at least another 160 lines of code in the header file, and you may have independently rediscovered why Microsoft C++ and especially MFC have pre compiled headers. The compiler has to do a lot of work and substitution for template.

You may want to allow the user to use the templates in the C++ source files for only the types they actually need rather than including the macro usage in the header file.

\$\endgroup\$
  • \$\begingroup\$ I chose to use macros, because I this particular case I think they are less error-prone w.r.t. setting the good names in the string, as the user may be tempted to remove the namespace, for example. Another reason is that I always find it strange to explicitly add code in the namespace of a library (though that's only my opinion) \$\endgroup\$ – Maliafo May 29 '17 at 17:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.