Pergunta

C ++ 11 introduz literais definidos pelo usuário que permitirá a introdução da nova sintaxe literal com base em existente literais (int, hex, string, float) para que qualquer tipo será capaz de ter uma apresentação literal.

Exemplos:

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

À primeira vista isso parece muito legal, mas eu estou querendo saber como aplicável é realmente, quando eu tentei pensar em ter os sufixos _AD e _BC criar datas I encontrados que é problemático devido ao fim do operador. 1974/01/06_AD seria primeiro avaliar 1974/01 (como ints simples) e só mais tarde o 06_AD (para não falar de Agosto e Setembro ter que ser escrito sem a 0 por razões octal). Isso pode ser contornado por ter a sintaxe ser 1974-1/6_AD para que a avaliação do operador de ordem obras mas é desajeitado.

Então, o que a minha pergunta se resume a isso, você sente esse recurso vai justificar-se? Que outros literais que gostaria de definir que fará sua C ++ código mais legível?


sintaxe Atualizado para ajustar o projecto final de junho de 2011

Foi útil?

Solução

Aqui está um caso onde há uma vantagem de usar literais definidos pelo usuário em vez de uma chamada de construtor:

#include <bitset>
#include <iostream>

template<char... Bits>
  struct checkbits
  {
    static const bool valid = false;
  };

template<char High, char... Bits>
  struct checkbits<High, Bits...>
  {
    static const bool valid = (High == '0' || High == '1')
                   && checkbits<Bits...>::valid;
  };

template<char High>
  struct checkbits<High>
  {
    static const bool valid = (High == '0' || High == '1');
  };

template<char... Bits>
  inline constexpr std::bitset<sizeof...(Bits)>
  operator"" _bits() noexcept
  {
    static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
    return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
  }

int
main()
{
  auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits;
  std::cout << bits << std::endl;
  std::cout << "size = " << bits.size() << std::endl;
  std::cout << "count = " << bits.count() << std::endl;
  std::cout << "value = " << bits.to_ullong() << std::endl;

  //  This triggers the static_assert at compile time.
  auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits;

  //  This throws at run time.
  std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits");
}

A vantagem é que uma exceção de tempo de execução é convertido em um erro de tempo de compilação. Você não pode adicionar o assert estático para o ctor bitset tomando uma corda (pelo menos não sem argumentos de modelo string).

Outras dicas

À primeira vista, parece ser um açúcar sintático simples.

Mas quando se olha mais profunda, vemos que é mais do que o açúcar sintático, como se estende opções do usuário do C ++ para criar tipos definidos pelo usuário que se comportam exatamente como distintos tipos built-in. Nesta, este pouco "bônus" é um muito interessante C ++ 11 além de C ++.

Será que realmente precisa dele em C ++?

Eu vejo alguns usos no código que eu escrevi nos últimos anos, mas só porque eu não usá-lo em C ++ não significa que não é interessante para outro desenvolvedor C ++ .

Nós tínhamos usado em C ++ (e em C, eu acho), literais definidos pelo compilador, para o tipo inteiro números como inteiros curtos ou longos, números reais como float ou double (ou mesmo long double), e cadeias de caracteres como normais ou caracteres de largura.

Em C ++, tivemos a possibilidade de criar nossos próprios tipos (ou seja, classes), com potencialmente nenhuma sobrecarga (inlining, etc.). Tivemos a possibilidade de adicionar operadores com seus tipos, para tê-los comportar-se como semelhante tipos built-in, que permite que desenvolvedores de C ++ para usar as matrizes e números complexos tão naturalmente como eles teriam se estes foram adicionados à própria linguagem. Podemos até mesmo adicionar operadores de conversão (que é geralmente uma má idéia, mas às vezes, é apenas a solução certa).

Ainda falta uma coisa para ter usuário tipos comportam-se como built-in tipos:. Literais definidos pelo usuário

Então, eu acho que é uma evolução natural para a linguagem, mas para ser o mais completo possível: " Se você deseja criar um tipo, e quer que ele se comporte como muito possível, como um built-in tipos , aqui estão as ferramentas ... "

Eu acho que é muito semelhante à decisão do .NET para fazer cada struct um primitivo, incluindo booleans, inteiros, etc., e têm todas as estruturas derivam de Object. Esta decisão só coloca .NET distante do alcance do Java ao trabalhar com primitivas, não importa o quanto o boxe / hacks unboxing Java irá adicionar à sua especificação.

Você realmente precisa-lo em C ++?

Esta questão é para YOU para responder. Não Bjarne Stroustrup. Não Herb Sutter. Não seja qual for membro do comitê padrão C ++. É por isso que você tem a opção em C ++ , e eles não vão restringir uma notação útil built-in tipos sozinho.

Se você precisa dele, então é uma adição bem-vinda. Se você não, bem ... não usá-lo. Vai custar-lhe nada.

Bem-vindo ao C ++, a linguagem onde os recursos são opcionais.

Bloated ??? Mostre-me sua complexos !!!

Existe uma diferença entre inchado e complexo (pun pretendido).

Como mostrado por Niels em o que novas capacidades não literais definidos pelo usuário adicionar à C ++ , ser capaz de escrever um número complexo é um dos dois recursos adicionais? 'recentemente' para C e C ++:

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

Agora, tanto C99 "complexo duplo de" tipo e C ++ "std :: complexo" tipo são capazes de se multiplicarem, adicionadas, subtraídas, etc., utilizando a sobrecarga de operador.

Mas no C99, que acabou de adicionar um outro tipo como um tipo interno e suporte embutido sobrecarga de operadores. E eles adicionaram um outro recurso interno literal.

Em C ++, eles apenas utilizado os recursos existentes da língua, viu que o recurso literal foi uma evolução natural da língua, e, assim, acrescentou ele.

Em C, se você precisa a mesma valorização notação para um outro tipo, você está sem sorte até que seu lobby para adicionar suas funções quântica de onda (ou pontos 3D, ou qualquer base de tipo que você está usando em seu campo de trabalho ) para o padrão de C como um embutido tipo sucede.

Em C ++ 11, você só pode fazê-lo:

Point p = 25_x + 13_y + 3_z ; // 3D point

É inchado? Não , a necessidade existe, como mostrado pela forma como ambos os complexos C e C ++ precisa encontrar uma maneira de representar seu comp literalvalores Lex.

É erroneamente concebido? Não , é concebido como todos os outros C ++ recurso, com extensibilidade em mente.

É apenas para fins de notação? Não , pois ele pode até mesmo adicionar segurança de tipo ao seu código.

Por exemplo, vamos imaginar um código orientado CSS:

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

Em seguida, é muito fácil de aplicar uma tipagem forte para a atribuição de valores.

Está é perigoso?

Boa pergunta. estas funções podem ser namespaced? Se sim, então Jackpot!

De qualquer forma, como tudo, você pode matar a si mesmo se a ferramenta é usada indevidamente . C é poderoso, e você pode atirar sua cabeça fora se você abusar a arma C. C ++ tem a arma C, mas também o bisturi, o taser, e qualquer outra ferramenta que você vai encontrar no kit de ferramentas. Você pode abusar do bisturi e sangrar até a morte. Ou você pode construir um código muito elegante e robusto.

recurso

Assim, como qualquer C ++, você realmente precisa dele? É a pergunta que você deve responder antes de usá-lo em C ++. Se não o fizer, ele vai te custar nada. Mas se você realmente precisa dele, pelo menos, a língua não vai deixar você para baixo.

O exemplo a data?

Seu erro, parece-me, é que você é operadores de mistura:

1974/01/06AD
    ^  ^  ^

Isso não pode ser evitado, porque / ser um operador, o compilador deve interpretá-lo. E, AFAIK, é uma coisa boa.

Para encontrar uma solução para o seu problema, eu iria escrever o literal de alguma outra forma. Por exemplo:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

Pessoalmente, eu escolheria o inteiro e as datas de ISO, mas isso depende de suas necessidades. Qual é o ponto de permitir que o usuário defina seus próprios nomes literais.

É muito bom para o código matemático. Fora de minha mente eu posso ver o uso para os seguintes operadores:

deg para graus. Isso torna a escrita ângulos absolutos muito mais intuitivo.

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

Ele também pode ser usado para várias representações ponto fixo (que ainda estão em uso no campo de DSP e gráficos).

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

Eles parecem agradáveis ??exemplos como usá-lo. Eles ajudam a fazer constantes no código mais legível. É mais uma ferramenta para código make ilegível bem, mas já temos tantas ferramentas de abusar de que mais uma não dói muito.

UDLs são namespaced (e podem ser importados usando declarações / directivas, mas você pode não explicitamente namespace um literal como 3.14std::i), o que significa que (espero) não vai ser uma tonelada de confrontos.

O fato de que eles realmente podem ser templated (e constexpr'd) significa que você pode fazer algumas coisas muito poderoso com UDLs. autores BigInt vai ser realmente feliz, como eles podem finalmente ter arbitrariamente grandes constantes, calculados em tempo de compilação (via constexpr ou modelos).

Eu estou apenas triste que não vamos ver um par de literais úteis no padrão (a partir da aparência dele), como s para std::string e i para a unidade imaginária.

A quantidade de tempo de codificação que será salvo por UDLs na verdade não é tão alto, mas a legibilidade será muito maior e mais e mais cálculos pode ser deslocado para compilar em tempo de execução mais rápida.

Deixe-me acrescentar um pouco de contexto. Para o nosso trabalho, literais definidos pelo usuário é muito necessária. Trabalhamos em MDE (Model-Driven Engineering). Queremos definir modelos e metamodelos em C ++. Nós realmente implementado um mapeamento de Ecore para C ++ ( EMF4CPP ).

O problema surge quando ser capaz de definir os elementos do modelo como classes em C ++. Estamos tomando a abordagem de transformar o metamodelo (Ecore) para modelos com argumentos. Argumentos do modelo são as características estruturais de tipos e classes. Por exemplo, uma classe com dois atributos int seria algo como:

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

Hoever, verifica-se que cada elemento em um modelo ou metamodelo, geralmente tem um nome. Nós gostaríamos de escrever:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

Mas, C ++, nem C ++ 0x não permitem isso, como cordas são proibidos como argumentos para modelos. Você pode escrever o nome caractere por caractere, mas isso é reconhecidamente uma bagunça. Com literais definidos pelo usuário adequadas, poderíamos escrever algo similar. Digamos que nós usamos "_n" para identificar nomes de elementos do modelo (Eu não uso a sintaxe exata, só para ter uma idéia):

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

Finalmente, ter essas definições como modelos nos ajuda muito a algoritmos de design para atravessar os elementos do modelo, transformações do modelo, etc, que são realmente eficientes, porque a informação tipo, identificação, transformações, etc. são determinados pelo compilador em tempo de compilação tempo.

Bjarne Stroustrup fala sobre UDL de neste C + +11 talk , na primeira seção em interfaces ricas do tipo, marca em torno de 20 minutos.

Seu argumento básico para UDLs assume a forma de um silogismo:

  1. "Trivial" tipos, ou seja, built-in tipos primitivos, só pode detectar erros tipo triviais. Interfaces com tipos mais ricos permitir que o sistema de tipos para pegar mais tipos de erros.

  2. Os tipos de erros de tipo que o código ricamente digitado pode pegar têm impacto no código real. (Ele dá o exemplo da Mars Climate Orbiter, que vergonhosamente falhou devido a um erro de dimensões em uma constante importante).

  3. No código real, as unidades são raramente utilizados. As pessoas não usá-los, porque incorrer em computação de tempo de execução ou sobrecarga de memória para criar tipos ricos é muito caro, e usando pré-existente C ++ templated código da unidade é tão notationally feio que ninguém utiliza. (Empiricamente, ninguém usa, mesmo que as bibliotecas foram em torno de uma década).

  4. Portanto, a fim de obter os engenheiros usem unidades em código real, nós necessário um dispositivo que (1) não incorre em sobrecarga de tempo de execução e (2) é notationally aceitável.

Apoio de tempo de compilação dimensão verificação é a única justificação exigida.

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

Veja por exemplo PhysUnits-CT-Cpp11 , uma pequena C ++ 11, C ++ 14 cabeçalho biblioteca -apenas para análise dimensional de tempo de compilação e / manipulação da quantidade unitária e de conversão. Mais simples do que Boost.Units , suporta unit tais como m, g, s, métrica prefixos tais como m, K, m, depende apenas de C ++ padrão biblioteca, SI-somente, poderes integrais de dimensões.

Hmm ... Eu não pensei sobre esse recurso ainda. Sua amostra foi bem pensado e é certamente interessante. C ++ é muito poderoso como é agora, mas, infelizmente, a sintaxe utilizada em pedaços de código que você lê é por vezes excessivamente complexa. Legibilidade é, se não todos, pelo menos muito. E essa característica uma seria orientada por mais legibilidade. Se eu tomar o seu último exemplo

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

... Eu me pergunto como você se expressar que hoje. Você teria um KG e uma classe LB e você comparar objetos implícitos:

assert(KG(1.0f) == LB(2.2f));

E isso faria bem. Com tipos que têm nomes com mais ou tipos que você não tem esperança de ter um bom construtor tal para sans escrevendo um adaptador, pode ser uma boa adição para o on-the-fly criação do objeto implícito e inicialização. Por outro lado, você já pode criar e inicializar objetos usando métodos, também.

Mas eu concordo com Nils em matemática. C e C ++ funções trigonométricas, por exemplo, exigem entrada em radianos. Eu acho que em graus porém, assim que um curto conversão implícita como Nils postada é muito agradável.

Em última análise, ele vai ser um açúcar sintático no entanto, mas ele vai ter um ligeiro efeito sobre a legibilidade. E provavelmente será mais fácil escrever algumas expressões muito (sin (180.0deg) é mais fácil de escrever do que o pecado (deg (180,0)). E, em seguida, haverá pessoas que abusam do conceito. Mas, em seguida, as pessoas de linguagem abusiva deve usar línguas muito restritivas em vez de algo tão expressiva como C ++.

Ah, meu post diz basicamente nada, exceto: ele vai ficar bem, o impacto não será muito grande. Não vamos preocupação. : -)

Eu nunca precisava ou queria esse recurso (mas isso poderia ser o efeito Blub ) . Minha reação de empurrão de joelho é que é coxo, e passível de recurso para as mesmas pessoas que pensam que é legal para operador de sobrecarga + para qualquer operação que poderia remotamente ser interpretado como a adição.

C ++ é geralmente muito rigoroso sobre a sintaxe utilizada - barrando o pré-processador não há muito que você pode usar para definir uma sintaxe custom / gramática. Por exemplo. podemos sobrecarregar operatos existente, mas não podemos definir novos - IMO isso é muito em sintonia com o espírito do C ++.

Eu não me importo de algumas maneiras para código-fonte mais personalizado - mas o ponto escolhido parece muito isolado para mim, o que me confunde mais.

uso Mesmo destina pode torná-lo muito mais difícil de ler o código fonte: uma única letra pode ter efeitos colaterais vastas longo alcance que de forma alguma podem ser identificados a partir do contexto. Com simetria para u, l e F, a maioria dos desenvolvedores irá escolher letras individuais.

Isso também pode transformar o escopo em um problema, usando letras isoladas no namespace global provavelmente será considerado uma má prática, e as ferramentas que são supostamente misturar bibliotecas mais fácil (namespaces e identificadores descritivos), provavelmente vai derrotar o seu propósito.

Eu vejo algum mérito em combinação com "auto", também em combinação com uma biblioteca de unidade como unidades impulso , mas não o suficiente para merecer este adition.

Eu me pergunto, no entanto, que idéias inteligente que venha com.

Eu usei literais do usuário para cadeias binárias como esta:

 "asd\0\0\0\1"_b

usando construtor std::string(str, n) para que \0 não iria cortar a corda ao meio. (O projeto faz um monte de trabalho com vários formatos de arquivo.)

Este foi útil também quando abandonou std::string em favor de um wrapper para std::vector.

Linha ruído naquela coisa é enorme. Também é horrível para ler.

Deixe-me saber, fizeram razão que nova adição sintaxe com qualquer tipo de exemplos? Por exemplo, eles têm alguns programas que já usam C ++ 0x?

Para mim, esta parte:

auto val = 3.14_i

não justifica esta parte:

std::complex<double> operator ""_i(long double d) // cooked form
{ 
    return std::complex(0, d);
}

Nem mesmo se você usar o i-sintaxe em 1000 outras linhas também. Se você escrever, você provavelmente escrever 10000 linhas em outra coisa ao longo desse bem. Especialmente quando você ainda vai provavelmente escrever principalmente em todos os lugares isso:

std::complex<double> val = 3.14i

-palavra-chave 'auto' pode ser que justificado, talvez apenas. Mas vamos dar apenas C ++, porque é melhor do que C ++ 0x neste aspecto.

std::complex<double> val = std::complex(0, 3.14);

É como .. tão simples. Ainda pensei que todas as DST e pontudos suportes são apenas coxo, se você usá-lo em todos os lugares. Eu não começar a adivinhar o que sintaxe existe em C ++ 0x para transformar std :: complex sob complexa.

complex = std::complex<double>;

Isso é talvez algo simples, mas eu não acredito que é tão simples em C ++ 0x.

typedef std::complex<double> complex;

complex val = std::complex(0, 3.14);

Talvez? > :)

De qualquer forma, o ponto é: a escrita 3.14i em vez de std :: complex (0, 3,14); não poupar muito tempo no geral, exceto em alguns casos super especial.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top