Выполнение функции времени компиляции С++

У меня есть строковые теги в моем коде, которые преобразуются в числа и используются для поиска значений в структуре значений тега.

У меня есть что-то вроде этого:

void foo()
{
    type value = search("SomeTag");
}

Где поиск определяется следующим образом:

type search(const char* tag)
{
    return internal_search(toNumber(tag));
}

Поскольку все временные теги являются постоянными во время компиляции, я хочу удалить вызов, который преобразует тег в число из функции поиска. Я знаю, что во время компиляции можно выполнить некоторые простые функции с помощью шаблонов (http://en.wikipedia.org/wiki/Compile_time_function_execution), но я точно не знаю, как итерации по нулевой завершаемой строке и сохранить промежуточные значения в шаблоне. Можете ли вы дать простой пример, который выполняет итерацию строки с завершающим нулем и добавляет символы в общедоступной переменной?

Ответы

Ответ 1

Вы не можете работать с строковыми литералами во время компиляции, поэтому то, что вы хотите, невозможно в том, как вы предложили. Однако, если вы планируете обрабатывать эти строки во время компиляции, это означает, что вы знаете все строки во время компиляции, и из этого вы можете прийти к приемлемым приближениям к тому, что вы хотите.

Показанный код подразумевает, что генерация числа (пусть называется хешем) вызывается каждый раз, когда кто-то ищет тег. Будет ли сокращение этого до одного вызова приемлемым? Если это так, вы можете определить константы и использовать их вместо строк:

const int SomeTag       = toNumber("SomeTag"      ); 
const int SomeOtherTag  = toNumber("SomeOtherTag" ); 
const int YetAnotherTag = toNumber("YetAnotherTag"); 
// ... 

Затем просто замените все события search("SomeTag") на search(SomeTag).

Если существует большое количество тегов, набрав указанное выше, может оказаться очень утомительным, и в этом случае макрос может помочь:

#define DEFINE_TAG(Tag_) const int Tag_ = toNumber(#Tag_); 

DEFINE_TAG(SomeTag); 
DEFINE_TAG(SomeOtherTag); 
DEFINE_TAG(YetAnotherTag); 
// ... 

#undef DEFINE_TAG

Ответ 2

Похоже, что вы хотите Boost. MPL boost::mpl::string. Было бы более или менее тривиально написать метафункцию для преобразования mpl::string в интегральный тип во время компиляции с использованием mpl::fold ( или не скомпилировать, если строковый литерал не представляет действительное целочисленное значение).

EDIT:

Я не совсем уверен, что вы ищете, так что в действительности это два разных ответа в зависимости от интерпретации:


IF то, что вы ищете, - это преобразование строки в интегральное значение во время компиляции (например, "425897" может быть признано интегральной константой 425897 во время компиляции) то можно использовать Boost.MPL, как я предложил:

#include <cstddef>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/char.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/end.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/minus.hpp>
#include <boost/mpl/negate.hpp>
#include <boost/mpl/next.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/reverse_fold.hpp>
#include <boost/mpl/size_t.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/times.hpp>
#include <boost/mpl/vector.hpp>

namespace details
{
    namespace mpl = boost::mpl;

    typedef mpl::vector10<
        mpl::char_<'0'>, mpl::char_<'1'>, mpl::char_<'2'>, mpl::char_<'3'>,
        mpl::char_<'4'>, mpl::char_<'5'>, mpl::char_<'6'>, mpl::char_<'7'>,
        mpl::char_<'8'>, mpl::char_<'9'>
    > valid_chars_t;

    template<typename IntegralT, typename PowerT>
    struct power_of_10;

    template<typename IntegralT, std::size_t Power>
    struct power_of_10<IntegralT, mpl::size_t<Power> > : mpl::times<
        power_of_10<IntegralT, mpl::size_t<Power - 1u> >,
        mpl::integral_c<IntegralT, 10>
    > { };

    template<typename IntegralT>
    struct power_of_10<IntegralT, mpl::size_t<1u> >
        : mpl::integral_c<IntegralT, 10>
    { };

    template<typename IntegralT>
    struct power_of_10<IntegralT, mpl::size_t<0u> >
        : mpl::integral_c<IntegralT, 1>
    { };

    template<typename IntegralT, typename StringT>
    struct is_negative : mpl::and_<
        boost::is_signed<IntegralT>,
        boost::is_same<
            typename mpl::front<StringT>::type,
            mpl::char_<'-'>
        >
    > { };

    template<typename IntegralT, typename StringT>
    struct extract_actual_string : mpl::eval_if<
        is_negative<IntegralT, StringT>,
        mpl::pop_front<StringT>,
        mpl::identity<StringT>
    > { };

    template<typename ExtractedStringT>
    struct check_valid_characters : boost::is_same<
        typename mpl::find_if<
            ExtractedStringT,
            mpl::not_<mpl::contains<valid_chars_t, mpl::_> >
        >::type,
        typename mpl::end<ExtractedStringT>::type
    > { };

    template<typename ExtractedStringT>
    struct pair_digit_with_power : mpl::first<
        typename mpl::reverse_fold<
            ExtractedStringT,
            mpl::pair<mpl::vector0<>, mpl::size_t<0> >,
            mpl::pair<
                mpl::push_back<
                    mpl::first<mpl::_1>,
                    mpl::pair<mpl::_2, mpl::second<mpl::_1> >
                >,
                mpl::next<mpl::second<mpl::_1> >
            >
        >::type
    > { };

    template<typename IntegralT, typename ExtractedStringT>
    struct accumulate_digits : mpl::fold<
        typename pair_digit_with_power<ExtractedStringT>::type,
        mpl::integral_c<IntegralT, 0>,
        mpl::plus<
            mpl::_1,
            mpl::times<
                mpl::minus<mpl::first<mpl::_2>, mpl::char_<'0'> >,
                power_of_10<IntegralT, mpl::second<mpl::_2> >
            >
        >
    > { };

    template<typename IntegralT, typename StringT>
    class string_to_integral_impl
    {
        BOOST_MPL_ASSERT((boost::is_integral<IntegralT>));

        typedef typename extract_actual_string<
            IntegralT,
            StringT
        >::type ExtractedStringT;
        BOOST_MPL_ASSERT((check_valid_characters<ExtractedStringT>));

        typedef typename accumulate_digits<
            IntegralT,
            ExtractedStringT
        >::type ValueT;

    public:
        typedef typename mpl::eval_if<
            is_negative<IntegralT, StringT>,
            mpl::negate<ValueT>,
            mpl::identity<ValueT>
        >::type type;
    };
}

template<typename IntegralT, typename StringT>
struct string_to_integral2
    : details::string_to_integral_impl<IntegralT, StringT>::type
{ };

template<typename IntegralT, int C0, int C1 = 0, int C2 = 0,
    int C3 = 0, int C4 = 0, int C5 = 0, int C6 = 0, int C7 = 0>
struct string_to_integral : string_to_integral2<
    IntegralT,
    boost::mpl::string<C0, C1, C2, C3, C4, C5, C6, C7>
> { };

Использование будет выглядеть так:

type search(int tag) { /*impl... */ }

void foo()
{
    type value = search(string_to_integral<int, '4258','97'>::value);
}

// OR, if you still want to maintain the separation
// between `search` and `internal_search`

type internal_search(int tag) { /*impl... */ }

template<typename TagStringT>
type search()
{
    return internal_search(string_to_integral2<int, TagStringT>::value);
}

void foo()
{
    typedef boost::mpl::string<'4258','97'> tag_t;
    type value = search<tag_t>();
}

Поддержка отрицательных чисел реализована, поддержка обнаружения переполнения не (но ваш компилятор, вероятно, даст предупреждение).


IF то, что вы ищете, - это сопоставление строк между целыми значениями во время компиляции (например, "SomeTag" может быть признано интегральной константой 425897 во время компиляции) то Boost.MPL все еще решает проблему, но все сопоставления между целыми целыми значениями должны быть известны во время компиляции и зарегистрированы централизованно:

#include <boost/type_traits/is_same.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/map.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/void.hpp>

namespace details
{
    namespace mpl = boost::mpl;

    typedef mpl::map<
        mpl::pair<
            mpl::string<'Some','Tag'>,
            mpl::integral_c<int, 425897>
        >,
        mpl::pair<
            mpl::string<'Some','Othe','rTag'>,
            mpl::integral_c<int, -87>
        >,
        mpl::pair<
            mpl::string<'AnUn','sign','edTa','g'>,
            mpl::integral_c<unsigned, 7u>
        >
    > mappings_t;

    template<typename StringT>
    struct map_string_impl
    {
        typedef typename mpl::at<
            mappings_t,
            StringT
        >::type type;
        BOOST_MPL_ASSERT_NOT((boost::is_same<type, mpl::void_>));
    };
}

template<typename StringT>
struct map_string2 : details::map_string_impl<StringT>::type { };

template<int C0, int C1 = 0, int C2 = 0, int C3 = 0,
    int C4 = 0, int C5 = 0, int C6 = 0, int C7 = 0>
struct map_string : map_string2<
    boost::mpl::string<C0, C1, C2, C3, C4, C5, C6, C7>
> { };

Использование будет выглядеть так:

type search(int tag) { /*impl... */ }

void foo()
{
    type value = search(map_string<'Some','Tag'>::value);
}

// OR, if you still want to maintain the separation
// between `search` and `internal_search`

type internal_search(int tag) { /*impl... */ }

template<typename TagStringT>
type search()
{
    return internal_search(map_string2<TagStringT>::value);
}

void foo()
{
    typedef boost::mpl::string<'Some','Tag'> tag_t;
    type value = search<tag_t>();
}

mappings_t - это то, что нужно отредактировать, чтобы поддерживать сопоставления с целыми целыми значениями, и, как показано, отображаемые интегральные значения не обязательно должны быть одного и того же базового типа.


В любом случае, поскольку отображение выполняется во время компиляции, search/internal_search (тот, который имеет реальную реализацию с использованием int), можно было бы сделать интегральное значение в качестве параметра шаблона, а не как параметр функции, если это имеет смысл для его реализации.

Надеюсь, это ответит на ваши вопросы.

Ответ 3

Если строковый литерал известен во время компиляции, то, вероятно, нет причин использовать его в виде строкового литерала. Вы можете использовать перечисление или именованные интегральные константы.

Если строка передается функции поиска переменной и она неизвестна во время компиляции, тогда нет способа сделать репликацию toNumber() во время компиляции. Тогда хорошим решением является использование какого-то словаря (например, std::map<std::string, int>)

Ответ 4

Пока не время компиляции, я думаю, что это достаточно быстро для вас;

void foo()
{
    const static auto someTagN = toNumber("SomeTag");
    type value = internal_search(someTagN );
}

Ответ 5

Я знаю, что это, вероятно, не модно, но вы можете генерировать хеш-таблицу раньше времени. Мне нравится использовать gperf для создания там сантехники.

Я знаю, знаю. Вы хотели что-то сделать компиляцию дольше... Просто говоря:)

Ответ 6

Не уверен, что можешь. Является ли список возможных тегов небольшим? Даже если нет, мало ли он в большинстве случаев. Если это так, вы можете использовать специализированную специализацию по подмножеству тегов

template<char *tag> 
int toNumber() {
    return toNumber(tag);
}

template<>
int toNumber<"0">() {
     return 0;
}

template<>
int toNumber<"1">() {
     return 1;
}

(оговорки: мой синтаксис специализации может быть неправильным, и я понятия не имею, работает ли это для char *)