Eu estou usando c ++ 17 com Boost.hana para escrever alguns programas de meta-programação. Uma questão que me surpreendeu é que tipo de expressão pode ser usada em um contexto constexpr como static_assert. Aqui está um exemplo:
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2.1
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
// static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
}
{ //test2.2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
Primeiro, escrevo uma classe X com dados de campo e um acessador getData () . No main () 's test1 parte, x1.data e x1.getData () se comportam mesmo que eu esperava. Mas na parte test2 , alterar o argumento para uma tupla boost :: hana static_assert(x2.data[0_c] == 1_c)
ainda se comporta bem, mas static_assert(x2.getData()[0_c] == 1_c)
falha na compilação, com o erro de ' leitura da variável não constexpr' x2 'não é permitido em uma expressão constante '. O que weired é se eu dividir x2.getData()[0_c]
em auto data = x2.getData();
e static_assert(data[0_c] == 1_c);
ele compila bem novamente. Eu esperava que eles se comportassem da mesma maneira. Então, alguém pode ajudar a explicar por x2.getData()[0_c]
que não pode ser usado em static_assert neste exemplo?
Para reproduzir: clang ++ 8.0 -I / caminho / para / hana-1.5.0 / include -std = c ++ 17 Test.cpp
fonte
constexpr
está faltando nox2
edata
,const
emgetData
. godbolt.org/z/ZNL2BKRespostas:
O problema é que
boost::hana::tuple
não possui um construtor de cópias.Ele tem um construtor que se parece com um construtor de cópia:
Mas, como esse é um modelo, não é um construtor de cópias .
Desde
boost::hana::tuple
não tem um construtor de cópia, um é declarado implicitamente e definidos como inadimplentes (que não é suprimida uma vezboost::hana::tuple
não tem nenhum Copiar ou mover construtores ou operadores de atribuição, porque, você adivinhou-lo, eles não podem ser modelos).Aqui vemos divergência de implementação , demonstrada no comportamento do seguinte programa:
O gcc aceita, enquanto Clang e MSVC rejeitam, mas aceitam se a linha não
#1
é comentada. Ou seja, os compiladores discordam sobre se o construtor de cópias definido implicitamente de uma classe não (diretamente) vazia é permitido para uso no contexto de avaliação constante.De acordo com a definição do construtor de cópias definido implicitamente, não há como o # 1 ser diferente do que
constexpr A(A const&) = default;
o gcc esteja correto. Observe também que, se dermos a B um construtor de cópias constexpr definido pelo usuário, Clang e MSVC novamente aceitarem, o problema parece ser que esses compiladores não conseguem rastrear a construtibilidade da cópia constexpr de classes copiáveis implicitamente copiáveis recursivamente vazias. Erros arquivados para MSVC e Clang ( corrigidos para Clang 11).Observe que o uso de
operator[]
é um arenque vermelho; a questão é se os compiladores permitem a chamadagetData()
(que cópia constróiT
) dentro de um contexto de avaliação constante comostatic_assert
.Obviamente, a solução ideal seria o Boost.Hana corrigir de
boost::hana::tuple
forma que ele tenha construtores de copiar / mover reais e operadores de atribuição de copiar / mover. (Isso resolveria seu caso de uso, pois o código estaria chamando construtores de cópia fornecidos pelo usuário, que são permitidos no contexto de avaliação constante.) Como solução alternativa , você pode considerargetData()
a possibilidade de hackear para detectar o caso de não-statefulT
:fonte
test2.2
parte aceita por Clang? (Editei a pergunta original e dividi o teste2 em teste2.1 e teste2.2) Eu esperava que eles se comportassem da mesma forma.hana::tuple
que ocorre no retorno degetData
. No teste2.2, a cópia ocorre fora do contexto de avaliação constante; portanto, Clang está bem com ela.getData()
não é permitido aqui, mas sair e introduzindo um temperal então aceita ..O problema é que você está tentando recuperar um valor de tempo de execução e testá-lo na compilação.
O que você pode fazer é forçar a expressão em tempo de compilação por meio de um
decltype
e ele funcionará como um encanto :).static_assert(decltype(x2.getData()[0_c]){} == 1_c);
Agora, a expressão é avaliada em tempo de compilação, portanto, o tipo é conhecido em tempo de compilação e, como também é construtível em tempo de computação, é possível usá-la em um static_assert
fonte
static_assert(decltype(x2.getData()[0_c]){} == 1_c)
pode funcionar bem, mas ainda quero salvar o arquivodecltype
porque isso pouparia muito. Eu acho que você está dizendo quex2.getData()
está recuperando um valor de tempo de execução para que ele não apareça em uma expressão static_assert. Então não entendo por que ax1.getData()
parte in test1 e os dados dadata[0_c]
parte test2 podem funcionar bem. Quais são as diferenças deles?Então, primeiro de tudo, você está perdendo o qualificador const no
getData()
método, então deve ser:Nenhuma variável é promovida, pelo menos do ponto de vista padrão, para ser constexpr se não estiver marcada como constexpr.
Observe que isso não é necessário para o
x1
tipoX
especializado em hana :: integral_constant, pois result of1_c
é um tipo sem construtor de cópias definido pelo usuário, que não contém nenhum dado internamente, portanto, uma operação de cópiagetData()
é de fato uma operação não operacional. , então expression:static_assert(x1.getData() == 1_c);
está correto, pois não há uma cópia real (não é necessário acesso aothis
ponteiro não const dex1
).Isso é muito diferente para o contêiner com o
hana::tuple
qual contém a construção de cópias factuais doshana::tuple
dados nox2.data
campo. Isso requer acesso factual ao seuthis
ponteiro - o que não era necessáriox1
, mas também não era uma variável constexpr.Isso significa que você está expressando sua intenção errada com ambos
x1
ex2
, e é necessário, pelo menos parax2
, marcar essas variáveis como constexpr. Observe também que o uso da tupla vazia, que é uma especialização geral basicamente vazia (sem construtores de cópias definidas pelo usuário)hana::tuple
, funciona perfeitamente (seção test3):fonte
x1
é um tipo vazio. Qualquer instância deX
possui um membro de dados. Tambémhana::tuple
conter tipos vazios também é vazio, pois usa a otimização de base vazia. Você pode estar no alvo culpando o construtor de cópias, porque Clang ou libc ++ podem estar fazendo algo complicadostd::integral_constant
.constexpr variable 'x3' must be initialized by a constant expression
const this
ponteiro e, infelizmente, o estiver usandox2
no caso static_assert. (no caso de x1 - é uma discussão mais aprofundada:)).hana::integral_constant
tenha um construtor padrão definido pelo compilador,hana::tuple
ele possui um definido pelo usuário. Além disso, como existe uma especialização para tupla vazia, que não possui construtor, o mesmo código para tupla vazia funciona: godbolt.org/z/ZeEVQN