2016-02-29 9 views
38

Rozszerzenie pakietu parametrów jest odwracane przez kompilator VS2015.Dlaczego ekspansja pakietu parametrów działa inaczej w przypadku różnych kompilatorów C++?

Mam następujący kod:

#include <iostream> 
#include <vector> 


template <typename... T> 
void f_Swallow(T &&...) 
{ 
} 

template <typename... T> 
std::vector<int> f(T ...arg) 
{ 
    std::vector<int> result; 
    f_Swallow 
    (
     [&]() 
     { 

      result.push_back(arg); 
      return true; 
     } 
     ()... 
    ) ; 
    return result; 
} 


using namespace std; 
int main() 
{ 
    auto vec = f(1,2,3,4); 

    for (size_t i = 0; i < vec.size(); ++i) 
     cout << vec[i] << endl; 
} 

Kiedy uruchomić ten kod w Xcode (szczęk-700.1.81), otrzymuję ten wynik:

1 
2 
3 
4 

ale sam przebieg kodu w VS2015 generuje to wyjście:

4 
3 
2 
1 

Dlaczego pakiety parametrów są różnie rozwijane w zależności od kompilatora? Czy istnieje sposób, aby to naprawić bez sprawdzania platformy i wersji kompilatora? Czy standard nie gwarantuje niczego w zamówieniu ekspansji?

+9

Czy to nie jest tylko nieokreślona kolejność oceny? (uwaga boczna: zazwyczaj w inicjatorze tablic idiomu "połknięcia" używany jest prosty inicjator, aby uniknąć tego problemu) – milleniumbug

+0

@milleniumbug, masz rację. I powinieneś opublikować to jako odpowiedź (choć z dodatkowym wyjaśnieniem). – StoryTeller

+2

Prawidłowy sposób to zrobić z listą inicjalizującą, która gwarantuje kolejność konstrukcji: 'auto dummy = {(result.push_back (arg), 0) ...};' [Demo] (http: // rextester .com/XHIYH83588) –

Odpowiedz

46

Nie jest to kolejność rozwijania pakietu parametrów, która jest inna, jest to kolejność analizowania argumentów funkcji.

f_Swallow 
(
    [&]() 
    { 

     result.push_back(arg); 
     return true; 
    } 
    ()... 
) ; 

Dla zwięzłości, pozwala tylko podać, że lambda nazwę funcN gdzie N jest numer parametru. Biorąc pod uwagę cztery argumenty, paczka parametr zostanie poszerzona dowolnym zgodnego kompilatora w tym:

f_Swallow(func1(), func2(), func3, func4()) ; 

Kolejność oceny argumentów funkcji jest nieokreślone w C++. Kompilator może je ocenić w kolejności (np. Twoja wersja Clang), w odwrotnej kolejności (jak twoja wersja MSVC) lub w dowolnej kolejności, którą lubi. Nie możesz liczyć na kolejność oceny.

Aby uzyskać to, co chcesz, możesz umieścić wyrażenia w kontekście, w którym określona jest kolejność oceny. Na przykład:

template <typename... T> 
std::vector<int> f(T ...arg) 
{ 
    std::vector<int> result; 
    (void)std::initializer_list<int> { (result.push_back(arg), 0)... }; 
    return result; 
} 

w C++ 17, będzie można wykonać następujące czynności z fold expressions:

template <typename... T> 
std::vector<int> f(T ...arg) 
{ 
    std::vector<int> result; 
    (result.push_back(arg), ...); 
    return result; 
} 
11

Pomyślałem, że może to być również napisany tak jak poniżej:

template <typename... T> 
std::vector<int> f(T ...arg) 
{ 
    std::vector<int> result{ arg... }; 
    return result; 
} 

Nie ma potrzeby, aby utworzyć std :: obojętne initializer_list

+0

Haha, tak, to najlepsze rozwiązanie dla przypadku, gdy chcesz po prostu stworzyć 'std :: vector' z twoje parametry. – TartanLlama

+0

Ciekawe, co jest w tym pożyteczne, _vs._ 'wektor' własny 'konstruktor' initializer_list', który tak czy inaczej wywołuje? Obecnie wygląda na to, że wszystko, co robi, to marnowanie linii tekstu, ale może brakuje mi czegoś oczywistego. –

+0

W moim przypadku funkcja była używana w tym 'auto vec = f();' w wielu miejscach, więc łatwiej było naprawić funkcję, a raczej zmienić wszystkie połączenia. –

Powiązane problemy