簡體   English   中英

如何在編譯時從類型創建靜態字符串

[英]How to create static strings from types at compile time

我有一堆有名字的類型。 (它們有更多功能,但為了討論起見,只有名稱是相關的。)這些類型及其名稱是在編譯時使用宏設置的:

#define DEFINE_FOO(Foo_)                        \
    struct Foo_ : public foo_base<Foo_> {       \
      static char const* name() {return #Foo_;} \
    }

然后將這些類型組合在編譯時列表(經典的簡單遞歸編譯時列表)中,我需要通過連接其對象的名稱來創建列表的名稱:

template<class Foo, class Tail = nil>
struct foo_list {
  static std::string name_list() {return Foo::name() + "-" + Tail::name();}
};
template<class Foo>
struct foo_list<Foo,nil> {
  static std::string name_list() {return Foo::name();}
};

代碼在這里歸結為它可能包含錯誤的程度,但實際上這很好用。

除了它在運行時創建然后復制相當長的字符串,這些字符串表示在編譯時實際上是眾所周知的類型。 由於這是一個在嵌入式設備上運行的性能相當敏感的代碼,我想改變這一點

  1. 列表的字符串理想地是在編譯時創建的,或者,如果沒有辦法在運行時創建它,並且
  2. 我只需要復制一個指向C字符串的指針,因為根據#1,字符串在內存中是固定的。
  3. 這與C ++ 03一起編譯,我們現在一直堅持使用它。

我怎樣才能做到這一點?

(如果這擴大了可用於此的臟技巧: foo對象的名稱只能由代碼創建和讀取,並且只有foo_list名稱字符串應該是人類可讀的。)

你可能想看看boost的mpl::string 一旦我的咖啡被踢進去的例子......

編輯:所以咖啡踢了...... :)

#include <iostream>

#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/vector.hpp>

namespace mpl = boost::mpl;

struct foo
{
  typedef mpl::string<'foo'> name;
};

struct bar
{
  typedef mpl::string<'bar'> name;
};

struct gah
{
  typedef mpl::string<'gah'> name;
};

namespace aux
{

template <typename string_type, typename It, typename End>
struct name_concat
{
  typedef typename mpl::insert_range<string_type, typename mpl::end<string_type>::type, typename mpl::deref<It>::type::name>::type base;
  typedef typename aux::name_concat<base, typename mpl::next<It>::type, End>::name name;
};

template <typename string_type, typename End>
struct name_concat<string_type, End, End>
{
  typedef string_type name;
};

}

template <typename ...Types>
struct type_list
{
  typedef mpl::string<> base;
  typedef mpl::vector<Types...> type_seq;
  typedef typename aux::name_concat<base, typename mpl::begin<type_seq>::type, typename mpl::end<type_seq>::type>::name name;
};

int main(void)
{
  typedef typename type_list<foo, bar, gah>::name tlist_name;
  std::cout << mpl::c_str<tlist_name>::value << std::endl;
}

我相信你有足夠的能力來根據你的情況調整上述內容。 注意:您將不得不忽略多字符常量警告...

幾個警告:傳遞給mpl::string的多字符常量不能超過4個字符,因此,有些如何分塊(或由單個字符構成),所以長字符串可以是, mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'>如果這是無法完成的,那么上面的代碼將不起作用..:/

我想出了以下解決方案:

類型生成為:

const char foo_str [] = "foo";
struct X
{
    static const char *name() { return foo_str; }
    enum{ name_size = sizeof(foo_str) };
};

這里的關鍵點是我們在編譯時知道其名稱的長度。 這允許我們計算類型列表中名稱的總長度:

template<typename list>
struct sum_size
{
    enum
    {
       value = list::head::name_size - 1 +
               sum_size<typename list::tail>::value
    };
};
template<>
struct sum_size<nil>
{
    enum { value = 0 };
};

知道編譯時的總長度,我們可以分配適當大小的靜態緩沖區來連接字符串 - 所以不會有任何動態分配:

static char result[sum_size<list>::value + 1];

該緩沖區應該在運行時填充,但只能填充一次,並且該操作非常便宜(比以前的解決方案快得多,動態分配字符串及其在遞歸中的連接):

template<typename list>
const char *concate_names()
{
    static char result[sum_size<list>::value + 1];
    static bool calculated = false;
    if(!calculated)
    {
        fill_string<list>::call(result);
        calculated = true;
    }
    return result;
}

這是完整的代碼:

Coliru現場演示

#include <algorithm>
#include <iostream>
using namespace std;

/****************************************************/

#define TYPE(X) \
const char X ## _str [] = #X; \
struct X \
{ \
    static const char *name() { return X ## _str; }  \
    enum{ name_size = sizeof(X ## _str) }; \
}; \
/**/

/****************************************************/

struct nil {};

template<typename Head, typename Tail = nil>
struct List
{
    typedef Head head;
    typedef Tail tail;
};

/****************************************************/

template<typename list>
struct sum_size
{
    enum { value = list::head::name_size - 1 + sum_size<typename list::tail>::value };
};
template<>
struct sum_size<nil>
{
    enum { value = 0 };
};

/****************************************************/

template<typename list>
struct fill_string
{
    static void call(char *out)
    {
        typedef typename list::head current;
        const char *first = current::name();
        fill_string<typename list::tail>::call
        (
            copy(first, first + current::name_size - 1, out)
        );
    }
};

template<>
struct fill_string<nil>
{
    static void call(char *out)
    {
        *out = 0;
    }
};

/****************************************************/

template<typename list>
const char *concate_names()
{
    static char result[sum_size<list>::value + 1];
    static bool calculated = false;
    if(!calculated)
    {
        fill_string<list>::call(result);
        calculated = true;
    }
    return result;
}

/****************************************************/

TYPE(foo)
TYPE(bar)
TYPE(baz)

typedef List<foo, List<bar, List<baz> > > foo_list;

int main()
{
    cout << concate_names<foo_list>() << endl; 
}

輸出是:

foobarbaz

PS你如何使用串聯字符串? 也許我們根本不需要生成連接字符串,從而減少了數據空間需求。

例如,如果你只需要打印字符串 - 那么

template<typename list>
void print();

就足夠了。 但缺點是,雖然數據量減小 - 這可能會導致代碼量增加。

  1. 您可以將字符串設置為static並且只需在運行時和僅在需要時構造字符串一次。
  2. 然后返回它們的const引用,這樣就不會有任何不必要的復制。

例:

template<class Foo, class Tail = nil>
struct foo_list {
  static const std::string& name_list() {
     static std::string names = Foo::name() + std::string("-") + Tail::name();
     return names;
  }
};

template<class Foo>
struct foo_list<Foo,nil> {
  static const std::string& name_list() {
     static std::string names = Foo::name();
     return names;
  }
};

可能不是你要寫的確切代碼,但我認為這給你的意義。 此外,您可以通過執行names.c_str()返回const char*

您可以考慮使用外部構建步驟而不是語言內解決方案。 例如,您可以編寫基於Clang的工具來解析相關文件,並在另一個TU中自動創建T::name實現。 然后將其集成到您的構建腳本中。

如果我們可以假設您唯一的要求是實際流式傳輸類的名稱 - 這意味着您不需要整個其他位置的連接字符串 - 您可以簡單地推遲流式傳輸但仍然可以從元編程中受益(因為Evgeny已經指出)。

雖然這個解決方案不能滿足您的要求#1(一個串聯字符串),但我仍然想為其他讀者指出一個解決方案。

這個想法是從所有T::name()函數構建一個地址序列,並在需要時將其傳遞給流函數,而不是通過編譯時的類型列表。 這是可能的,因為具有外部鏈接的變量可以用作模板非類型參數。 當然,您的里程數可能會因數據和代碼大小而異,但除非您處於高性能環境中,否則我希望此方法至少同樣適合,因為在運行時不必創建其他字符串。

請注意,我故意使用可變參數模板(在C ++ 03中不可用),因為它更易讀,更容易推理。

這里有 “小提琴”。

#include <ostream>
#include <boost/core/ref.hpp>
#include <boost/bind.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/for_each.hpp>

namespace mpl = boost::mpl;


template<typename>
class foo_base
{};

#define DECLARE_FOO(Foo_) \
    struct Foo_ : public foo_base<Foo_> { \
        static char const* name() {return #Foo_;} \
    };


// our own integral constant because mpl::integral_c would have to be specialized anyway
template<typename T, T Value>
struct simple_integral_c
{
    operator T() const { return Value; }
};

template<typename T, T ...Values>
struct ic_tuple : mpl::vector<simple_integral_c<T, Values>...>
{};


typedef const char*(*NameFunction)();

template <NameFunction ...Functions>
struct function_list : ic_tuple<NameFunction, Functions...>
{};

template <typename ...Types>
struct function_of_list : function_list<&Types::name...>
{};


struct print_type
{
    void operator ()(std::ostream& os, NameFunction name)
    {
        if (nth++)
            os << "-";
        os << name();
    }

    print_type(): nth(0) {}

private:
    int nth;
};

// streaming function
template<NameFunction ...Functions>
std::ostream& operator <<(std::ostream& os, function_list<Functions...>)
{
    mpl::for_each<function_list<Functions...>>(
        boost::bind<void>(print_type(), boost::ref(os), _1)
    );

    return os;
}

現在用C ++ 14可能會用像hana這樣強大的庫編寫解決方案,看看這個hana小提琴

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM