標籤:

C++模版元編程中如何拼接兩個const char*?

例如:

template&

struct Strcat

{

static constexpr Value=...//這裡該怎麼寫?

};

//main()

std::cout&<&::Value;


如果利用宏,上面有別人貼過的爆棧的link應該夠用了

如果潔癖不利用宏,必須先把const char[]類型的string literal轉為可以其他類型,以便下手。

原因是C++裡面built-in數組沒有constexpr的構造函數,僅有{}形式一種(修正:這裡其實是因為gcc一個bug導致數組的built-in copy constructor不被認為constexpr,clang是支持的)

這裡為了保證構造函數constexpr,取巧利用了一個C++規範外,但是基本是事實標準的class layout,即單一繼承情況下,base class的成員在derived class的成員之前。

template &
struct String : String& {
constexpr String(const char(str)[N]) : String&(str), c(str[N-1]) {}
protected:
template &
constexpr String(const char(str)[M]) : String&(str), c(str[N-1]) {}
char c;
};
template &<&>
struct String&<0&> {
template &
constexpr String(T...) {}
};

這樣進行任何String的實例即可生成一個memory layout和const char[]一致的實例

如String&<6&>("hello")。

可以用個輔助函數來使用時避開模板參數

template &
constexpr String& mkString(const char(str)[N]) { return String&(str);}
constexpr String&<0&> mkString(const char(str)[0]) { return String&<0&>(str);}

這樣使用時只需要mkString("hello")即可。

然後有了這個String類,想拼接就很容易啦,我這裡上一個偷懶的版本,即String&+String&後結果不為String&而是其他類型。完全是因為我懶。因為要寫成String&的話,String類的構造函數要寫的很複雜,留作思考題大家回去做作業

template &
struct StringCat : String&
{
T t;
constexpr StringCat(const char(str)[M], const T str2) : String&(str), t(str2) {}
};

template &
constexpr StringCat&&> catString(const char(str)[M], const char(str2)[N]) {
return StringCat&&>(str, mkString(str2));
}

這樣使用時,只要直接用auto + catString即可

(這裡catString暴露的介面是2個const char ()[],你可以在中間增加別的介面,例如直接用上面的String類,同時你也可以考慮用variadic template來實現多個字元串的拼接)

const char hello[] = "hello, ";
const char world[] = "world!!";
//這裡hello和world聲明為array而不是pointer
//是因為String類的構造函數做了保護性檢查,public構造函數僅接受const char [SIZE]的array
//可以修改String類的構造函數的參數為const char *
//來使用const char *類型的字元常量
const auto helloworld = catString(hello, world);

生成彙編如下

helloworld:
.byte 104
.byte 101
.byte 108
.byte 108
.byte 111
.byte 44
.byte 32
.byte 119
.byte 111
.byte 114
.byte 108
.byte 100
.byte 33
.byte 33
.byte 0

需要const char*的話,reinterpret_cast&(helloworld)


這個問題非常有意思,C++模版元編程中如何拼接兩個const char*?這個答案我是支持的,Template recursion的問題是

  • 出來的類都是奇葩類,而且重要的是你concat結束了之後很有可能作為一個參數要傳到某個函數裡面的,這個函數一般不會說設計的接受這些個奇葩類的,那你這時候還得寫各種cast。
  • 其實folly::fatal 的reflection也是用這裡的回答解決的問題,而且fb現在所有的thrift reflection走fatal的話都是像之前回答裡面一樣做compile time concatenation的,問題是這樣用template解決問題的時候binary文件各種爆炸

前幾天一個同事Eric Niebler終於妥善解決了這個問題(commit在這裡add folly::FixedString, a constexpr-usable string with a fixed-size i… · facebook/folly@77d5e54)(其實之前有個東西叫folly::StringPiece類似大家的答案,要是大家有興趣的話我可以補充),寫了一個東西叫folly::FixedString,基本上就是akirya 的答案加上無數的cast/helper 函數,還支持replace。比如

constexpr auto hello = makeFixedString("hello"); // a FixedString&<5&>
constexpr auto world = makeFixedString("world"); // another FixedString&<5&>
constexpr auto hello_world = hello + " " + world + "!";
static_assert(hello_world == "hello world!", "w00t");
EXPECT_STREQ("hello world!", hello_world.c_str());

還比如

FixedString&<10&> test{"****"};
test.replace(1, 2, "!!!!");
EXPECT_STREQ("*!!!!*", test.c_str());
static_assert(makeFixedString("****").creplace(1, 2, "!!!!") == "*!!!!*", "");

各種cast都支持哦,folly安裝了之後直接用,誰用誰知道呀


很遺憾不能,C++語言層沒有提供區分字元串字面值和const char *以及char[]的機制,必須用宏


貌似在哪裡見到過。也思考過。但沒實際寫過。

可能這樣想的:可以遞歸兩個字元串,遇到0結束。用一個類裝一個靜態char數組 (長度由上面的遞歸推導)。再遞歸的把結果放入這個類的靜態char數組成員里。

但最後一步做不到,那是運行時。

能做到的是保存結果到一個字元串的另外一種形式(用一個繼承的類型來表示,類似tuple)


如果是常量字元串的話,時可以拼接成std::array&這樣的

#include&
#include&
#include&
#include&
#include&
using std::cout;
using std::endl;

// strcat on Compile-time

namespace detail
{
namespace detail
{
template &
constexpr std::array& , N&>
to_array_impl( T( a )[N] , std::index_sequence& )
{
return{ { a[I]... } };
}
}

template &
constexpr std::array& , N&> to_array( T( a )[N] )
{
return detail::to_array_impl( a , std::make_index_sequence&{} );
}

template &
constexpr std::array& to_array( const T c )
{
return{ {c,0} };
}

template &
constexpr std::array &< T , N1 + N2 - 1 &>
concat_string_impl( const std::array& a , std::index_sequence& , const std::array&b , std::index_sequence& )
{
return{ { a[I1]... , b[I2]... } };
}

template &
constexpr std::array &< T , N1 + N2 - 1 &>
concat_string_impl( const std::array& a , const std::array&b )
{
typedef std::make_index_sequence &< N1 - 1 &> type1;
typedef std::make_index_sequence& type2;
return concat_string_impl( a , type1() , b , type2() );
}

template &
constexpr auto
concat_string_impl( const std::array& a , const std::array&b , const std::array& ... args)
{
typedef std::make_index_sequence &< N1 - 1 &> type1;
typedef std::make_index_sequence& type2;
return concat_string_impl( concat_string_impl( a , type1() , b , type2() ) , args... );
}
}

template&
constexpr auto concat_string( Args... args )
{
return detail::concat_string_impl( detail::to_array( args )... );
}

template&
void outputArray( const std::array& value )
{
cout &<&< typeid( value ).name() &<&< " " &<&< value.data() &<&< endl; } constexpr auto a = concat_string( "11111abc" , "1" , "ABC" , "!!!" ,"%" ); constexpr auto b = concat_string( "abc" , "1234" ); int main() { outputArray( concat_string( "abc" , "1234" ) ); outputArray( concat_string( "a" , "1234" ) ); outputArray( concat_string( "abc" , "1" ) ); outputArray( concat_string( "abc" , "1" , "ABC" , "!!!" ,"%" ) ); return 0; }


這…沒問題?


如果是literal string的話…… 直接拼起來就行了

#define COMBINE_PSTR(str1,str2) str1##str2


編譯時期拼接const char*還是有可能的,見How to concatenate a const char* in compile time

但是語法不允許string literal直接作為模板參數吧。


模板的感覺單就const char *不夠,我來作弊一下,給出一個宏版本的,也是編譯期完成字元串連接的。

#include&

#define STRCAT(STRING_L,STRING_R) STRING_L STRING_R

template &
void get_str_information(const char (str)[length])
{
std::cout &<&< "the string is " &<&< str &<&< std::endl; std::cout &<&< "the length is " &<&< length&<&

win10 vs 2013 和 Ubuntu gcc 5.4均編譯通過


不懂為啥strcat還能擼模板。如果是偏特化const char*的話可以使用char *str=const_cast&(cstr)來去除const修飾。然後直接strcat();

如果通用const char*或者char*,那可以利用C++的string庫直接return (string(cstr1)+string(cstr2)).c_str();不過會返回const char*,可以const_cast.


推薦閱讀:

windows有沒有類似xcode的軟體,寫完代碼可以run一下?
已經邁入30的程序員,還能幹幾年,以後該怎麼辦呢?
C語言兩數定義正確,相乘溢出的原因?
做 C 語言編譯器前端的難度如何?

TAG:編程 | C | CC |