Est-il possible de concaténer deux chaînes de type `const char *` lors de la compilation?


12

Évidemment, nous pouvons concaténer deux littéraux de chaîne dans une constexprfonction, mais qu'en est-il de la concaténation d'un littéral de chaîne avec une chaîne renvoyée par une autre constexprfonction comme dans le code ci-dessous?

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>);

Le code rend l'identificateur de chaîne indépendant du compilateur d'un type arithmétique.

EDIT1:

Un exemple un peu plus compliqué est:

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 tableau std contenant les octets serait-il acceptable? Sinon, vous pouvez utiliser des macros et du code gen pour ce faire.
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont oui, il semble qu'il n'y ait pas de meilleure solution que std::array(et probablement + modèles
variadic

@ Yakk-AdamNevraumont concaténant std :: arrays: stackoverflow.com/questions/42749032/… , exemple en direct: wandbox.org/permlink/VA85KCTqxiyS2rKE
Dmitriano

1
Il semble que vous essayez essentiellement de rouler à la main quelque chose qui donne le même type de résultat que l' typeidopérateur. Une partie de la raison typeidfait partie du langage (par exemple pris en charge par un mot-clé de langage dédié) plutôt qu'une fonction de bibliothèque est que l'implémentation repose sur la "magie du compilateur" - il n'est pas possible d'implémenter dans le langage sans un support dédié de l'implémentation .
Peter

1
@Dmitriano Oui, vous remarquerez peut-être que j'ai déjà vu cette question ;)
Yakk - Adam Nevraumont

Réponses:


4

Voici une classe de chaîne de temps de compilation rapide:

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>;

vous pouvez l'utiliser comme ceci:

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>();
}

ce qui conduit à des déclarations comme:

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

qui passe.

Exemple en direct .

Maintenant, une chose ennuyeuse est que la longueur du tampon est dans le système de type. Vous pouvez ajouter un lengthchamp et définir Nbe "buffer size" et le modifier ct_strpour ne copier que lengthet laisser les octets de fin sous 0. Remplacez ensuite common_typepour renvoyer le maximum Ndes deux côtés.

Cela vous permettrait de passer ct_str{"uint"}et ct_str{"int"}dans le même type de valeur et de rendre le code d'implémentation un peu moins ennuyeux.

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>;

Les implémentations de fonctions deviennent maintenant:

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>();
}

ce qui est beaucoup plus naturel à écrire.

Exemple en direct .


Cool! Il est préférable d'utiliser elseen get_arithmetic_sizeavec if constexprmême si vous le faites return, parce que sans elsel'affirmation dependent_false_v<T>échouera.
Dmitriano

La deuxième alternative est extrêmement cool!
Dmitriano

4

Non c'est impossible. Vous pouvez implémenter quelque chose comme ci-dessous (c'est 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

Si vous n'aimez pas utiliser <cmath>, vous pouvez remplacer 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::logest trop compliqué pour moi, besoin d'une technique générique pour concaténer les cordes
Dmitriano

C'est un constexpr, ne t'inquiète pas std::log(). Vous pouvez le remplacer, mais le code sera agrandi,
SM

avez-vous un exemple pour EDIT1?
Dmitriano

4
À ma connaissance, ni std::logni std::strcmpn'est garanti d'être constexpr. En fait, la norme leur interdit spécifiquement d'être constexprdepuis C ++ 14. Par conséquent, votre code utilise en fait des extensions non standard.
LF
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.