Concatenate compile-time strings in a template at compile time?
Asked Answered
K

4

5

Currently I have:

template <typename T> struct typename_struct<T*> {
    static char const* name() { 
        return (std::string(typename_struct<T>::name()) + "*").c_str(); 
    }
};

I wonder if I can avoid the whole bit where I'm forced to allocate a string to perform the concatenation.

This is all happening at compile time, i.e. I intend to get the string "int****" when I reference typename_struct<int****>::name(). (Do assume that I have declared a corresponding specialization for int which returns "int")

As the code is written now, does the compiler do the concatenation with std::string during compile time only? (I would be okay with that) Or does such a call result in 4 std::string based concatenations at runtime? (I would not be okay with that)

Kati answered 16/7, 2014 at 14:26 Comment(7)
Avoiding the bit where you return an invalid pointer (to freed memory) would be even better.Safeconduct
@MikeSeymour Good point. It seems like setting the return type to char const* is just not practicalKati
std::string uses dynamic memory so I can think of no reason for doing this at compile type, especially not for performance.Unteach
@NeilKirk the point is that I don't need to use std::stringKati
Having a real compile-time C++ string type would be a great tool to have in the standard, and it's feasible, but it hasn't happened yet. This recent proposal is interesting, though.Contraoctave
Also see discussion here.Contraoctave
Check this proposal, which unfortunately did not make it in C++17. Regardless, it is quite easy to implement in C++11/14.Lacee
C
5

You could use something like this. Everything happens at compile time. Specialize base_typename_struct to define your primitive types.

template <const char* str, int len, char... suffix>
struct append {
  static constexpr const char* value() {
    return append<str, len-1, str[len-1], suffix...>::value();
  }
};

template <const char* str, char... suffix>
struct append<str, 0, suffix...> {
  static const char value_str[];
  static constexpr const char* value() {
    return value_str;
  }
};

template <const char* str, char... suffix>
const char append<str, 0, suffix...>::value_str[] = { suffix..., 0 };


template <typename T>
struct base_typename_struct;

template <>
struct base_typename_struct<int> {
  static constexpr const char name[] = "int";    
};


template <typename T, char... suffix>
struct typename_struct {
  typedef base_typename_struct<T> base;
  static const char* name() {
    return append<base::name, sizeof(base::name)-1, suffix...>::value();
  }
};

template <typename T, char... suffix>
struct typename_struct<T*, suffix...>:
  public typename_struct<T, '*', suffix...> {
};


int main() {
  cout << typename_struct<int****>::name() << endl;
}
Cramer answered 16/7, 2014 at 15:16 Comment(10)
I think there may be a way to automatically turn int into 'i','n','t' using a macro. ThanksKati
This is awesome, but I think it is limited by not being able to concatenate constexpr strings, it only allows for appending single char's. Any ideas for making it slightly more powerful so I can do arbitrary concats? Marco's link seems like a lead. I do believe it's possible to have a macro that fetches (compile time of course) a sub index of a char* string... what we need is something that takes "int" into 'i','n','t'Kati
The specific use case would be e.g. obtaining the string "int* Array" out of the type int*[]Kati
@StevenLu, You can do it with a macro: EXPAND("int") => 'i', 'n', 't'. Using BOOST_PP_REPEAT with some sort of PushBack<str, c> does the trick. ElaborationKilliecrankie
With generalized constexpr functions, it becomes more feasible to use a constexpr class that returns new instances upon modification (a la pure functions).Killiecrankie
@StevenLu, maybe look at Sprout? Looks like it has a compile-time std::string work-alike. Looks very comprehensive. Mentioned in the other answer of Marco's link. https://mcmap.net/q/472823/-constexpr-with-string-operations-workaroundCramer
Quite the big can of worms I opened. Thanks for all the links everyone. Enough stuff to wade through for weeks here. Worst part is, my code already works! It just doesn't have compile-time strings for the extra final bit of speed. So I can barely justify going down this rabbit hole. Still, Sprout actually looks really good.Kati
Wondering if the above code is standard - Visual Studio 2015 spits the following: error C4576: a parenthesized type followed by an initializer list is a non-standard explicit type conversion syntax and refuses to compile.Belsky
Ah!, you can make it work in Visual Studio 2015 by having a static static const char value_str[]; initialized with { suffix..., 0 }, and return value_str in value()Belsky
Yeah, that was a GCC extension. I edited the code. I was surprised to get a comment on an old answer about C++ esoterica :)Cramer
H
5

Alternative way without using recursive templates (but requires C++14):

#include <utility>
template<int...I> using is      = std::integer_sequence<int,I...>;
template<int N>   using make_is = std::make_integer_sequence<int,N>;

constexpr auto size(const char*s) { int i = 0; while(*s!=0){++i;++s;} return i; }

template<const char*, typename, const char*, typename>
struct concat_impl;

template<const char* S1, int... I1, const char* S2, int... I2>
struct concat_impl<S1, is<I1...>, S2, is<I2...>> {
    static constexpr const char value[]
    {
        S1[I1]..., S2[I2]..., 0
    };
};

template<const char* S1, const char* S2>
constexpr auto concat {
    concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value
};

Example:

constexpr const char a[] = "int";
constexpr const char c[] = "**";

#include <iostream>
int main()
{
    std::cout << concat<a,b> << '\n';
}

append characters to string can also be implemented like this, by replacing the second const char* parameter with char....

Hengel answered 29/6, 2017 at 1:14 Comment(1)
If I try constexpr auto a = "123"; constexpr auto b = "456"; constexpr auto c = concat<a, b>; I get an error. Any clue why?Phenobarbital
C
0

I'm not sure of what you're searching for but I believe you're interested in a combination of typeid and name-demangling (which compiler are you using?)

In gcc it would be something like

#include<iostream>
#include <string>
#include <typeinfo>
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
using namespace std;

std::string demangle(const char* name) {
    int status = -4; // some arbitrary value to eliminate the compiler warning

    // enable c++11 by passing the flag -std=c++11 to g++
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}

template <typename T> struct typename_struct {
  static std::string name() {
    std::string typeName = typeid(T).name();
    return demangle(typeName.c_str());
  }
};

int main(){

  cout << typename_struct<int****>::name(); // Prints "int****"

  return 0;
}

http://ideone.com/nLsFF0

Sources: https://mcmap.net/q/103327/-unmangling-the-result-of-std-type_info-name

As for your question: those aren't constexpr constructs, thus the evaluation happens at runtime although the templated parameters and code are instantiated at compile-time.

Using templates doesn't mean every instruction contained in there will be executed and resolved at "compile-time".

I believe you can't achieve this whole bunch of stuff at compile-time since there are de-mangling functions (ABI-specific) involved. If I interpreted your question wrong please let me know.

Contact answered 16/7, 2014 at 14:50 Comment(5)
Right, I know about typeid and name-demangling, I am doing that in parallel (so I can check against it -- it normally makes template instantiations' types ugly). My project here goes about achieving "reflection" in a different way, so I'm trying to see if I can define templates to cover all the bases. It's been working well so far. Now you say they are not constexpr constructs, is it because I'm using std::string to do the concatenation? Is there a way for me to force the concatenation to happen at compiletime if I can make all these templates also constexprs (if that even makes any sense)?Kati
It's actually not a great big deal if each "pointer-chain" induces a linear-time string concatenation operation at runtime. Mainly because I only need this for "compatibility". Use of these insane types is ill-advised in almost all cases.Kati
@StevenLu There are several constraints for constexpr functions, take a look here: en.cppreference.com/w/cpp/language/constexpr . This won't work with std::string, you might want to also take a look here for string concatenation: https://mcmap.net/q/472823/-constexpr-with-string-operations-workaroundContact
@StevenLu: You can't perform reflection in a useful way without compiler support.Emlyn
@ Marco Awesome!!!! Thanks a lot for the links. @Emlyn Well I'm not done yet, but when I can show you the evidence for it, I will. C++11 has a nice bag of tricks. It may be sufficient. (Hint. Epic quantities of preprocessor macros and templates)Kati
E
0
#include <iostream>

//
***************************************************************************
template<const char* S1, const char* S2, size_t I1 = 0, size_t I2 = 0, char = S1[I1], char = S2[I2], char... Chars>
struct Concat : Concat<S1, S2, I1 + 1, I2, S1[I1 + 1], S2[I2], Chars..., S1[I1]>
{
};

// ****************************************************************************
template<const char* S1, const char* S2, size_t I1, size_t I2, char C2, char... Chars>
struct Concat<S1, S2, I1, I2, 0, C2, Chars...> : Concat<S1, S2, I1, I2 + 1, 0, S2[I2 + 1], Chars..., S2[I2]>
{
};

// ****************************************************************************
template<const char* S1, const char* S2, size_t N1, size_t N2, char... Chars>
struct Concat<S1, S2, N1, N2, 0, 0, Chars...>
{
  static constexpr const char Text[] = { Chars... , 0 };
};

// ****************************************************************************
static constexpr const char A[] = "123";
static constexpr const char B[] = "456";

// ****************************************************************************
int main(int argc, char* argv[]){
  std::cout << Concat<A, B>::Text << std::endl;
  return 0;
}
Eldwin answered 5/6, 2022 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.