È possibile concatenare due stringhe di tipo `const char *` al momento della compilazione?


12

Ovviamente possiamo concatenare due letterali di stringa in una constexprfunzione, ma per quanto riguarda la concatenazione di un letterale di stringa con una stringa restituita da un'altra constexprfunzione come nel codice seguente?

template <class T>
constexpr const char * get_arithmetic_size()
{
    switch (sizeof(T))
    {
    case 1: return "1";
    case 2: return "2";
    case 4: return "4";
    case 8: return "8";
    case 16: return "16";
    default: static_assert(dependent_false_v<T>);
    }
}

template <class T>
constexpr std::enable_if_t<std::is_arithmetic_v<T>, const char *> make_type_name()
{
    const char * prefix = std::is_signed_v<T> ? "int" : "uint";
    return prefix; // how to concatenate prefix with get_arithmetic_size<T>() ?
}

static_assert(strings_equal(make_type_name<int>, make_type_name<int32_t>);

Il codice rende un identificatore di stringa indipendente dal compilatore di tipo aritmetico.

Edit1:

Un esempio un po 'più complicato è:

template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {};

template<template<typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref> : std::true_type {};

template <class T>
constexpr std::enable_if_t<is_specialization<T, std::vector>::value || is_specialization<T, std::list>::value, const char *> make_type_name()
{
    return "sequence"; // + make_type_name<typename T::value_type>;
}

static_assert(strings_equal(make_type_name<std::vector<int>>(), make_type_name<std::list<int>>()));

2
Un array standard contenente i byte sarebbe accettabile? Altrimenti, puoi usare macro e code gen per farlo.
Yakk - Adam Nevraumont l'

@ Yakk-AdamNevraumont sì, sembra che non ci sia una soluzione migliore di std::array(e probabilmente + modelli variadici)
Dmitriano

@ Yakk-AdamNevraumont concatenando std :: array: stackoverflow.com/questions/42749032/… , esempio live: wandbox.org/permlink/VA85KCTqxiyS2rKE
Dmitriano

1
Sembra che tu stia essenzialmente cercando di tirare a mano qualcosa che ottiene lo stesso tipo di risultato typeiddell'operatore. Parte del motivo typeidè parte del linguaggio (ad esempio supportato da una parola chiave di linguaggio dedicata) piuttosto che una funzione di libreria è che la sua implementazione si basa sulla "magia del compilatore" - non è possibile implementare nella lingua senza un supporto dedicato dall'implementazione .
Peter

1
@Dmitriano Sì, potresti notare che ho già visto quella domanda in precedenza ;)
Yakk - Adam Nevraumont

Risposte:


4

Ecco una classe di stringhe di tempo di compilazione rapida:

template<std::size_t N>
struct ct_str
{
    char state[N+1] = {0};
    constexpr ct_str( char const(&arr)[N+1] )
    {
        for (std::size_t i = 0; i < N; ++i)
            state[i] = arr[i];
    }
    constexpr char operator[](std::size_t i) const { return state[i]; } 
    constexpr char& operator[](std::size_t i) { return state[i]; } 

    constexpr explicit operator char const*() const { return state; }
    constexpr char const* data() const { return state; }
    constexpr std::size_t size() const { return N; }
    constexpr char const* begin() const { return state; }
    constexpr char const* end() const { return begin()+size(); }

    constexpr ct_str() = default;
    constexpr ct_str( ct_str const& ) = default;
    constexpr ct_str& operator=( ct_str const& ) = default;

    template<std::size_t M>
    friend constexpr ct_str<N+M> operator+( ct_str lhs, ct_str<M> rhs )
    {
        ct_str<N+M> retval;
        for (std::size_t i = 0; i < N; ++i)
            retval[i] = lhs[i];
        for (std::size_t i = 0; i < M; ++i)
            retval[N+i] = rhs[i];
        return retval;
    }

    friend constexpr bool operator==( ct_str lhs, ct_str rhs )
    {
        for (std::size_t i = 0; i < N; ++i)
            if (lhs[i] != rhs[i]) return false;
        return true;
    }
    friend constexpr bool operator!=( ct_str lhs, ct_str rhs )
    {
        for (std::size_t i = 0; i < N; ++i)
            if (lhs[i] != rhs[i]) return true;
        return false;
    }
    template<std::size_t M, std::enable_if_t< M!=N, bool > = true>
    friend constexpr bool operator!=( ct_str lhs, ct_str<M> rhs ) { return true; }
    template<std::size_t M, std::enable_if_t< M!=N, bool > = true>
    friend bool operator==( ct_str, ct_str<M> ) { return false; }
};

template<std::size_t N>
ct_str( char const(&)[N] )->ct_str<N-1>;

puoi usarlo in questo modo:

template <class T>
constexpr auto get_arithmetic_size()
{
    if constexpr (sizeof(T)==1)
        return ct_str{"1"};
    if constexpr (sizeof(T)==2)
        return ct_str{"2"};
    if constexpr (sizeof(T)==4)
        return ct_str{"4"};
    if constexpr (sizeof(T)==8)
        return ct_str{"8"};
    if constexpr (sizeof(T)==16)
        return ct_str{"16"};
}

template <class T, std::enable_if_t<std::is_arithmetic<T>{}, bool> = true>
constexpr auto make_type_name()
{
    if constexpr (std::is_signed<T>{})
        return ct_str{"int"} + get_arithmetic_size<T>();
    else
        return ct_str{"uint"} + get_arithmetic_size<T>();
}

che porta a dichiarazioni come:

static_assert(make_type_name<int>() == make_type_name<int32_t>());

passando.

Esempio dal vivo .

Ora una cosa fastidiosa è che la lunghezza del buffer è nel sistema di tipi. È possibile aggiungere un lengthcampo ed Nessere "dimensione buffer" e modificare ct_strper copiare lengthe lasciare solo i byte finali come 0. Quindi eseguire common_typel' override per restituire il massimo Ndi entrambi i lati.

Ciò ti consentirebbe di passare ct_str{"uint"}e ct_str{"int"}nello stesso tipo di valore e rendere il codice di implementazione un po 'meno fastidioso.

template<std::size_t N>
struct ct_str
{
    char state[N+1] = {0};

    template<std::size_t M, std::enable_if_t< (M<=N+1), bool > = true>
    constexpr ct_str( char const(&arr)[M] ):
        ct_str( arr, std::make_index_sequence<M>{} )
    {}
    template<std::size_t M, std::enable_if_t< (M<N), bool > = true >
    constexpr ct_str( ct_str<M> const& o ):
        ct_str( o, std::make_index_sequence<M>{} )
    {}
private:
    template<std::size_t M, std::size_t...Is>
    constexpr ct_str( char const(&arr)[M], std::index_sequence<Is...> ):
        state{ arr[Is]... }
    {}
    template<std::size_t M, std::size_t...Is>
    constexpr ct_str( ct_str<M> const& o, std::index_sequence<Is...> ):
        state{ o[Is]... }
    {}
public:
    constexpr char operator[](std::size_t i) const { return state[i]; } 
    constexpr char& operator[](std::size_t i) { return state[i]; } 

    constexpr explicit operator char const*() const { return state; }
    constexpr char const* data() const { return state; }
    constexpr std::size_t size() const {
        std::size_t retval = 0;
        while(state[retval]) {
            ++retval;
        }
        return retval;
    }
    constexpr char const* begin() const { return state; }
    constexpr char const* end() const { return begin()+size(); }

    constexpr ct_str() = default;
    constexpr ct_str( ct_str const& ) = default;
    constexpr ct_str& operator=( ct_str const& ) = default;

    template<std::size_t M>
    friend constexpr ct_str<N+M> operator+( ct_str lhs, ct_str<M> rhs )
    {
        ct_str<N+M> retval;
        for (std::size_t i = 0; i < lhs.size(); ++i)
            retval[i] = lhs[i];
        for (std::size_t i = 0; i < rhs.size(); ++i)
            retval[lhs.size()+i] = rhs[i];
        return retval;
    }

    template<std::size_t M>
    friend constexpr bool operator==( ct_str lhs, ct_str<M> rhs )
    {
        if (lhs.size() != rhs.size()) return false;
        for (std::size_t i = 0; i < lhs.size(); ++i)
            if (lhs[i] != rhs[i]) return false;
        return true;
    }
    template<std::size_t M>
    friend constexpr bool operator!=( ct_str lhs, ct_str<M> rhs )
    {
        if (lhs.size() != rhs.size()) return true;
        for (std::size_t i = 0; i < lhs.size(); ++i)
            if (lhs[i] != rhs[i]) return true;
        return false;
    }
};

template<std::size_t N>
ct_str( char const(&)[N] )->ct_str<N-1>;

Le implementazioni delle funzioni ora diventano:

template <class T>
constexpr ct_str<2> get_arithmetic_size()
{
    switch (sizeof(T)) {
        case 1: return "1";
        case 2: return "2";
        case 4: return "4";
        case 8: return "8";
        case 16: return "16";
    }

}

template <class T, std::enable_if_t<std::is_arithmetic<T>{}, bool> = true>
constexpr auto make_type_name()
{
    constexpr auto base = std::is_signed<T>{}?ct_str{"int"}:ct_str{"uint"};
    return base + get_arithmetic_size<T>();
}

che è molto più naturale da scrivere.

Esempio dal vivo .


Freddo! E 'meglio usare elsein get_arithmetic_sizecon if constexpr, anche se lo si fa return, perché senza elsel'asserzione dependent_false_v<T>fallirà.
Dmitriano,

La seconda alternativa è estremamente interessante!
Dmitriano,

4

No, è impossibile. Puoi implementare qualcosa come sotto (è C ++ 14).

#include <cmath>
#include <cstring>
#include <iostream>
#include <type_traits>

constexpr const char* name[] = {
  "uint1", "uint2", "uint4", "uint8", "uint16",
  "int1",  "int2",  "int4",  "int8",  "int16"
};

template <class T>
constexpr std::enable_if_t<std::is_arithmetic<T>::value, const char *> make_type_name() {
  return name[std::is_signed<T>::value * 5 +
    static_cast<int>(std::log(sizeof(T)) / std::log(2) + 0.5)];
}

static_assert(std::strcmp(make_type_name<int>(), make_type_name<int32_t>()) == 0);

int main() {
  std::cout << make_type_name<int>();
  return 0;
}

https://ideone.com/BaADaM

Se non ti piace usare <cmath>, puoi sostituire std::log:

#include <cstring>
#include <iostream>
#include <type_traits>

constexpr const char* name[] = {
  "uint1", "uint2", "uint4", "uint8", "uint16",
  "int1",  "int2",  "int4",  "int8",  "int16"
};

constexpr size_t log2(size_t n) {
  return (n<2) ? 0 : 1 + log2(n/2);
}

template <class T>
constexpr std::enable_if_t<std::is_arithmetic<T>::value, const char *> make_type_name() {
  return name[std::is_signed<T>::value * 5 + log2(sizeof(T))];
}

static_assert(std::strcmp(make_type_name<int>(), make_type_name<int32_t>()) == 0);

int main() {
  std::cout << make_type_name<int>();
  return 0;
}

std::logè troppo complicato per me, ho bisogno di una tecnica generica per concatenare le stringhe
Dmitriano

È un constexpr, non preoccuparti std::log(). Puoi sostituirlo, ma il codice verrà ingrandito,
SM

hai un esempio per EDIT1?
Dmitriano,

4
Per quanto a mia conoscenza, non std::logne std::strcmpè garantito per essere constexpr. In effetti, lo standard proibisce loro di essere constexprdal C ++ 14. Pertanto, il codice utilizza effettivamente estensioni non standard.
LF
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.