metaprogrammingdefs.h 8.97 KB
/* Copyright (C) 2019
 *
 * This file is part of the osdev components suite
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 */
#ifndef OSDEV_COMPONENTS_MQTT_METAPROGRAMMINGDEFS_H
#define OSDEV_COMPONENTS_MQTT_METAPROGRAMMINGDEFS_H

#include <cstddef> // for std::size_t
#include <sstream>
#include <string>

#include "utils.h"

/**
 * @brief Create a type trait that checks for the existence of a member in a struct.
 * @param memberName The member name
 *
 * A call such as OSDEV_COMPONENTS_HASMEMBER_TRAIT(onSuccess) leads to a type trait has_onSuccess.
 */
#define OSDEV_COMPONENTS_HASMEMBER_TRAIT(memberName)                                                                      \
    template <typename T, typename = void>                                                                             \
    struct has_##memberName : public std::false_type                                                                   \
    {                                                                                                                  \
    };                                                                                                                 \
                                                                                                                       \
    template <typename T>                                                                                              \
    struct has_##memberName<T, osdev::components::mqtt::void_t<decltype(std::declval<T>().memberName)>> : public std::true_type \
    {                                                                                                                  \
    };

/**
 * @brief Create a type trait that checks for the existence of a member method with no parameters.
 * @param methodName The method name
 * @param postfix An extra postfix that is added to the type trait struct.
 *                This makes it possible create type traits that check for
 *                overloaded methods.
 *
 * A call such as OSDEV_COMPONENTS_HASMETHOD_TRAIT(capacity,1) leads to a type trait has_capacity1.
 */
#define OSDEV_COMPONENTS_HASMETHOD_TRAIT(methodName, postfix)                                                                        \
    template <typename T, typename = void>                                                                                        \
    struct has_##methodName##postfix : public std::false_type                                                                     \
    {                                                                                                                             \
    };                                                                                                                            \
                                                                                                                                  \
    template <typename T>                                                                                                         \
    struct has_##methodName##postfix<T, osdev::components::mqtt::void_t<decltype(std::declval<T>().methodName())>> : public std::true_type \
    {                                                                                                                             \
    };

/**
 * @brief Create a type trait that checks for the existence of a member method with 1 or more parameters.
 * @param methodName The method name
 * @param postfix An extra postfix that is added to the type trait struct.
 *                This makes it possible create type traits that check for
 *                overloaded methods.
 * @param ... Variable number of arguments. These can be values, but also declval constructs such as std::declval<MyClass>().
 *
 * A call such as OSDEV_COMPONENTS_HASMETHODWP_TRAIT(capacity,,"string") leads to a type trait has_capacity that
 * checks for the existence of a method capacity that accepts a const char pointer.
 */
#define OSDEV_COMPONENTS_HASMETHODWP_TRAIT(methodName, postfix, ...)                                                                            \
    template <typename T, typename = void>                                                                                                   \
    struct has_##methodName##postfix : public std::false_type                                                                                \
    {                                                                                                                                        \
    };                                                                                                                                       \
                                                                                                                                             \
    template <typename T>                                                                                                                    \
    struct has_##methodName##postfix<T, osdev::components::mqtt::void_t<decltype(std::declval<T>().methodName(__VA_ARGS__))>> : public std::true_type \
    {                                                                                                                                        \
    };

namespace osdev {
namespace components {
namespace mqtt {

/**
 * @brief Used to detect ill formed types in a SFINAE context.
 * If the parameter pack Ts contains an invalid type, struct make_void cannot be expanded.
 */
template <typename... Ts>
struct make_void
{
    using type = void;
};

/**
 * @brief Introduced in c++17 (but will also work in c++11)
 */
template <typename... Ts>
using void_t = typename make_void<Ts...>::type;

/// @brief Build an index list that can be used to traverse another list (f.i. a char array).
/// Recursive definition.
/// Start with an empty indices list. Build the indices list recursively.
/// upper-1, indices... becomes the indices... in the next recursive call.
template <std::size_t lower, std::size_t upper,
    template <std::size_t...> class meta_functor, std::size_t... Is>
struct apply_bounded_range
{
    typedef typename apply_bounded_range<lower, upper - 1, meta_functor, upper - 1, Is...>::result result;
};

/// @brief Terminator of apply_bounded_range.
/// Terminates when the upper bound (which runs) is equal to the lower bound.
template <std::size_t lower, template <std::size_t...> class meta_functor, std::size_t... Is>
struct apply_bounded_range<lower, lower, meta_functor, Is...>
{
    typedef typename meta_functor<Is...>::result result;
};

/**
 * @brief Helper method to perform static_assert on the expected count of a parameter pack.
 */
template <std::size_t expectedCount, typename... Args>
void static_assert_count()
{
    constexpr std::size_t actualCount = sizeof...(Args);
    static_assert(expectedCount == actualCount, "Unexpected parameter count.");
}

/**
 * @brief Helper method to convert a type to std::string.
 * @param d_first The output iterator to write the converted string to.
 * @param arg The argument to convert to std::string.
 */
template <typename OutputIt, typename T>
int apply_to_string_one(OutputIt d_first, const T& arg)
{
    std::ostringstream ss;
    ss << arg;
    *d_first++ = ss.str();
    return 0;
}

/**
 * @brief Converts all parameters to std::string.
 * @param d_first The output iterator to write the converted strings to.
 * @param args The arguments to convert to std::string.
 */
template <typename OutputIt, typename... Args>
void apply_to_string(OutputIt d_first, Args&&... args)
{
    // d_first is not used when args parameter pack is empty.
    apply_unused_parameters(d_first);

    // We need to use the expand_type trick in order to be able to use an initializer list
    //  so that we can call the method for every variadic argument
    using expand_type = int[];
    // Array must be initialized with values. Add a single value so that the array can be initialized when the parameter pack is empty.
    expand_type et{ 0, apply_to_string_one(d_first, std::forward<Args>(args))... };
    apply_unused_parameters(et);
}

/**
 * @brief Helper that removes const, volatile and reference from a type.
 * @note Defined in std C++20.
 */
template <typename T>
struct remove_cvref
{
    using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
};

}       // End namespace mqtt
}       // End namespace components
}       // End namespace osdev

#endif  // OSDEV_COMPONENTS_MQTT_METAPROGRAMMINGDEFS_H