【问题标题】:C++ compile time function executionC++编译时函数执行
【发布时间】:2011-05-03 12:28:36
【问题描述】:

我的代码中有字符串标签,它们被转换为数字并用于在标签-值结构中搜索值。

我有这样的事情:

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) 执行一些简单的函数,但我不知道如何遍历以空结尾的字符串并将中间值保留在模板中。你能给出一个简单的例子来迭代一个空终止的字符串并将字符添加到一个公共变量中吗?

【问题讨论】:

  • 作为替代方案,也许toNumber 可以保留它所看到的所有标签及其数值的地图,这样您只需为每个标签支付一次转换费用?它不如使用模板语言在编译时进行评估效率高,但我敢打赌,这样代码的可读性和可维护性会更高。
  • TTBOMK 你不能在编译时迭代字符串。
  • @aroth 我认为 toNumber 函数比搜索快,即使我会使用 log2 搜索。
  • @sbi:你可以;看看 boost::mpl::string。但是你必须像这样写'all ','your',' str','ings'(利用char实际上可以是4个字符的事实)。
  • 我明白了,它们实际上不是数字字符串。为什么不使用预处理器和通用头文件? #define TAG_SPEED 12414121你可以直接从数据库生成这个文件。

标签: c++ template-meta-programming


【解决方案1】:

听起来你想要的是Boost.MPLboost::mpl::string。在编译时使用mpl::fold 编写一个元函数将mpl::string 转换为整数类型或多或少是微不足道的(或者如果字符串文字不代表有效的整数值,则编译失败)。

编辑:

我不完全确定您在寻找什么,所以这里实际上是两个不同的答案,具体取决于解释:


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)可以将整数值作为模板参数而不是如果这样做对其实现有意义,则作为函数参数。

希望这能回答您的问题。

【讨论】:

  • 我不会说这是微不足道的(至少对我来说不是),但它可以做到。
  • 请给我一个实现。这是投票最多的答案,但我认为这是不可能的,正如其他人所说。当我有一个完整的答案时,我会投票。
  • @Felics :编辑了完整的实现。我提供了两种方法,因为我不清楚你真正在寻找什么......
  • 我需要将字符串映射到 int 之类的“SomeTag”到 1234567,而您的回答是错误的,因为您将 int 映射到 int('abcd' 是 int,而不是字符串)
  • @Felics :字符串的定义是:一个字符序列。虽然'Some' 的直接类型可能是int,但语义它通过boost::mpl::string 公开为一个字符序列,因此它实际上是一个字符串。你真正要说的是你不喜欢'Some','Tag'"SomeTag"语法,这是你可以在我输入之前 days 确定的为你出代码,省了我的麻烦;但事实上,它们都是字符串。将来,请先阅读您链接到的文档,然后再浪费其他人的时间来询问代码,然后轻而易举地忽略它。
【解决方案2】:

您不能在编译时对字符串文字进行操作,因此您想要的内容在您建议的方式中是不可行的。但是,如果您打算在编译时处理这些字符串,那么这意味着您在编译时就知道所有字符串,并且您可能会得到您想要的可接受的近似值。

您展示的代码暗示每次有人搜索标签时都会调用数字生成(我们称之为哈希)。将其减少为一次调用是否可以接受?如果是这样,您可以定义常量并使用它们来代替字符串:

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

【讨论】:

  • 这可能是个好主意,因为它解决了 enum 的大问题(我不需要在所有项目文件中包含标头并在每次添加新标签时重新编译它)和本地定义(标签冲突)。这里唯一的问题是在不同文件中重新定义相同的标签和链接器错误,但是.. 这可以用 static const int 而不是 const int 来解决。谢谢!
  • 那么您是否使用字符串是因为您不想将枚举包含在任何地方?我想你知道你实际上是在用编译时性能换取运行时安全吗?这真的值得吗?在这种情况下,我过去所做的是将值空间(在您的情况下:enum 值)分解为在不同标头中定义的独立块(您必须改用const int)。然后你只包含你感兴趣的一个标题,并且不受其他标题更改的影响。
【解决方案3】:

如果字符串文字在编译时已知,那么可能没有理由将其用作字符串文字。您可以使用枚举或命名的整数常量。

如果字符串通过变量传递给搜索函数并且在编译时是未知的,那么在编译时没有办法执行toNumber() resulution。那么一个好的解决方案是使用某种字典(例如std::map&lt;std::string, int&gt;

【讨论】:

  • 前面说了,toNumber函数比搜索快。
  • 我认为这里的第一段是关键。要在编译时执行此操作,您需要在编译时知道所有可能的字符串。有了这些,就没有理由不为它们使用符号常量。
【解决方案4】:

虽然不是编译时间,但我认为这对你来说已经足够快了;

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

【讨论】:

  • 它不会在编译时运行,而是在全局构建时运行,因此会延长程序启动时间。
  • 这是我想要避免的,在运行时进行字符串转换。我没有使用多次的标签,我有只使用一次或几次的不同标签。
  • @Jan Hudec 它会在第一次调用 foo 时运行,但是如果一旦调用 foo 这没有优化效果,因为标签会像以前一样在运行时转换一次,我会将不再使用的垃圾号码填满全局内存。
  • @Jan:实际上,本地static 变量在程序执行第一次通过它们的初始化代码时被初始化。简而言之:someTagN 在第一次调用 foo() 时被初始化。
【解决方案5】:

我知道它可能不流行,但你可以提前生成一个哈希表。我喜欢使用 gperf 在那里生成管道。

我知道知道。你想要一些东西来让编译持续更长时间......只是说:)

【讨论】:

  • 这不是问题,调试可能是#define X(tag) toNumber(tag) 和发布#define X(tag) someTemplate::number。这是为了运行时优化,与编译时间无关。
  • 我是在开玩笑,我不担心编译时间 :)
  • 编译时间是个大问题。在我当前的项目中,完全重建可能需要几个小时,这是一个非常大的问题:)
【解决方案6】:

不确定是否可以。可能的标签列表是否很小?即使不是,大多数时候它都很小。如果是这样,您可以对标签的子集使用模板特化

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

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

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

(警告:我的专业化语法可能是错误的,我不知道这是否适用于 char*)

【讨论】:

  • 模板不接受字符文字,只接受常量地址。因此,您必须为字符串创建 const char * const 变量并将这些常量传递给模板。
  • 字符串文字是左值,因此不能是编译时常量。但是,character 文字确实可以是编译时常量。
猜你喜欢
  • 2016-11-11
  • 2016-07-24
  • 2014-11-05
  • 2016-05-21
  • 1970-01-01
  • 2022-11-28
  • 1970-01-01
  • 2016-06-28
  • 1970-01-01
相关资源
最近更新 更多