2015-12-27 16 views
14

Ho una classe C++ che ha il seguente interfaccia:Un temporizzatore C++ dovrebbe essere costante?

class F { 
public: 
    F(int n, int d); 
    // no other constructors/assignment constructors defined 
    F& operator *= (const F&); 
    F& operator *= (int); 
    int n() const; 
    int d() const; 
}; 

E ho il seguente codice:

const F a{3, 7}; 
const F b{5, 10}; 
auto result = F{a} *= b; // How does this compile? 

in Visual Studio (VS) 2013, la linea ha commentato compila senza errori. Sotto VS2015, errore C2678 viene prodotto:

error C2678: binary '*=': no operator found 
    which takes a left-hand operand of type 'const F' 
    (or there is no acceptable conversion) 
note: could be 'F &F::operator *=(const F &)' 
note: or  'F &F::operator *=(int)' 
note: while trying to match the argument list '(const F, const F)' 

La mia aspettativa era che F{a} creerebbe una copia non-const temporanea di a a che si sarebbero applicate operator *= (b), dopo di che l'oggetto temporaneo sarebbe stato assegnato a result. Non mi aspettavo che il temporaneo fosse una costante. È interessante notare che: auto result = F(a) *= b; compila senza errori in VS2015, che ho pensato dovrebbe essere semanticamente lo stesso.

La mia domanda è: quale comportamento è corretto VS2015 o VS2013 & perché?

Molte grazie

+1

Sembra un errore a prima vista, 'F {a}' dovrebbe essere non costante –

+0

puoi pubblicare un MCVE solo per assicurarti che non ci siano altri fattori? –

+2

Evidentemente ha perso le sue biglie quando ha tentato di dedurre un elenco di inizializzatori (const F, const F). Usa invece 'F (a)'. Utilizzare connect.microsoft.com per segnalare bug. –

risposta

0

I miei pensieri si tratta di un bug in VS2015, perché se si specifica definita dall'utente costruttore di copia:

F(const F&); 

o fare a codice non-const variabile sarà compilato con successo.

Sembra che la costanza dell'oggetto da a sia stata trasferita nell'oggetto appena creato.

+0

Cosa fare se il copyctor è esplicito? Mi sembra che lo stia usando come cast perché non c'è bisogno di una copia autentica. –

8

Visual Studio 2015 non produce il risultato corretto per:

F{a} 

Il risultato dovrebbe essere un prvalue (gcc e clang entrambi hanno questo risultato), ma sta producendo un lvalue. Sto usando la seguente versione modificata del codice del PO per produrre questo risultato:

#include <iostream> 

class F { 
public: 
    F(int n, int d) :n_(n), d_(d) {}; 
    F(const F&) = default ; 
    F& operator *= (const F&){return *this; } 
    F& operator *= (int) { return *this; } 
    int n() const { return n_ ; } 
    int d() const { return d_ ; } 
    int n_, d_ ; 
}; 

template<typename T> 
struct value_category { 
    static constexpr auto value = "prvalue"; 
}; 

template<typename T> 
struct value_category<T&> { 
    static constexpr auto value = "lvalue"; 
}; 

template<typename T> 
struct value_category<T&&> { 
    static constexpr auto value = "xvalue"; 
}; 

#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value 

int main() 
{ 
    const F a{3, 7}; 
    const F b{5, 10}; 
    std::cout << "\n" << VALUE_CATEGORY(F{a}) << "\n"; 
} 

Punta di cappello a Luc Danton for the VALUE_CATEGORY() code.

Visual Studio utilizzando webcompiler che ha una versione relativamente recente produce:

lvalue 

che deve essere const in questo caso per generare l'errore che stiamo vedendo. Mentre sia gcc e clang (see it live) producono:

prvalue 

questo potrebbe essere correlato alla altrettanto sconcertante Visual Studio bug std::move of string literal - which compiler is correct?.

Nota possiamo ottenere lo stesso problema con gcc e clang utilizzando un const F:

using cF = const F ; 
auto result = cF{a} *= b; 

quindi non solo è Visual Studio dandoci la categoria valore errato, ma anche arbitrariamente aggiunta di un cv-qualificazione .

Come ha notato Hans nei suoi commenti alla tua domanda utilizzando F(a) produce i risultati attesi poiché produce correttamente un valore di prvalue.

La sezione del progetto standard C++ è sezione 5.2.3[expr.type.conv] che dice:

Analogamente, un semplice tipo-specificatore o typename-specificatore seguito da un braced- init-list crea un oggetto temporaneo del tipo specificato direct-list-initialized (8.5.4) con l'elenco braced-init specificato e il suo valore è quell'oggetto temporaneo come valore di prvalue.

nota, per quanto posso dire questo non è il "old MSVC lvalue cast bug". La soluzione a questo problema è utilizzare /Zc:rvalueCast che non risolve questo problema. Questo problema si differenzia anche nell'aggiunta non corretta di un qualificatore cv che, per quanto ne so, non si verifica con il problema precedente.

+0

oh, è il vecchio bug di lvalue di MSVC. Pensavo che non avrebbero perpetuato quello per elencare l'inizializzazione ... –

+0

@ M.M come fai a saperlo? Per quanto ne so, la soluzione [come menzionato qui] (http://stackoverflow.com/a/26508755/1708801) usa '/ Zc: rvalueCast' che non risolve questo problema. –

+0

Lo so dalla lettura della risposta (?) –

0

Visual C++ ha avuto un errore per qualche tempo in cui un cast di identità non produce un valore temporaneo, ma fa riferimento alla variabile originale.

Bug report qui: identity cast to non-reference type violates standard

+0

Non credo sia lo stesso bug, per quanto ho capito è lo stesso problema di [questa domanda] (http://stackoverflow.com/a/26508755/1708801) e la soluzione è quella di utilizzare '/ Zc: rvalueCast' che non risolve il problema qui. –

0

Da http://en.cppreference.com/w/cpp/language/copy_elision:

Nei seguenti casi, i compilatori sono autorizzati a omettere il della copia e spostare-costruttori di oggetti di classe anche se costruttore di copia/spostamento e il distruttore ha effetti collaterali osservabili.

.......

Quando un senza nome temporaneo, non vincolata a riferimenti, verrebbe spostato o copiati in un oggetto dello stesso tipo (ignorando primo livello CV- qualificazione), la copia/spostamento è omessa. Quando quel temporaneo è costruito, viene costruito direttamente nella memoria dove sarebbe altrimenti essere spostato o copiato in. Quando il temporaneo senza nome è l'argomento di un'istruzione return, questa variante di copia elision è nota come RVO, "ottimizzazione del valore di ritorno".

Quindi il compilatore ha la possibilità di ignorare la copia (che in questo caso si comporterebbe come un cast implicito per il tipo non-const).

Problemi correlati