[英]Passing a vector or a vector of vectors or a vector of ... (you get the idea) to a function
我正在為機器學習編寫 API,我需要一個重載函數,該函數可以接收向量作為參數,也可以接收向量向量(用於批處理作業)。
不過,我在調用該函數時遇到了一些問題。
作為一個更簡單的示例,該函數可能如下所示:
void bar( const std::vector<float>& arg ) {
std::cout << "BAR: Vector of float" << std::endl;
}
void bar( const std::vector<std::vector<float>>& arg ) {
std::cout << "BAR: Vector of vectors of float" << std::endl;
}
所以,我希望我可以這樣稱呼它:
bar( { 1,2,3 } );
bar( { { 1,2,3 } } );
但是在第二個 IDE 中,IDE 抱怨說,兩個重載函數都匹配參數列表,因此我必須像這樣調用它才能使其工作。
bar( { { { 1,2,3 } } } );
這是為什么? 那不是向量的向量(即“3D-向量”)嗎?
當我傳遞一個先前初始化的向量時也是如此:
std::vector<float> v = { 1,2,3,4,5 };
bar( v );
bar( { v } );
兩者都打印出BAR: Vector of float
消息。 所以我現在必須去:
bar( { { { v } } } );
讓它工作,這現在看起來像一個 4D 向量。 我錯過了什么嗎?
歡迎來到地獄。 當你有
bar( { 1,2,3 } );
{ 1,2,3 }
被視為std::initializer_list<float>
並且唯一可行的調用函數是void bar( const std::vector<float>& arg )
當你有
bar( { { 1,2,3 } } );
現在{ { 1,2,3 } }
可以被解釋為外括號表示一個std::vector<float>
,並且內的括號std::initializer_list<float>
它,或者可以是一個的構造std::initializer_list<std::initializer_list<float>>
用於構造二維向量。 任何一個選項都一樣好,所以你會產生歧義。 正如你所發現的,解決方案是
bar( { { { 1,2,3 } } } );
所以現在最外面的一組大括號表示創建一個std::vector<std::vector<float>>
而第二個最外面的大括號表示一個std::initializer_list<std::initializer_list<float>>
,並且最內層大括號是其中的一個元素。
和
bar( v );
bar( { v } );
它有點復雜。 顯然bar( v );
會做你想做的,但是bar( { v } );
實際上與bar( { { 1,2,3 } } );
因為[over.ics.list]中的規則。 具體來說,第 7 段說{ v }
是通過復制構造函數創建std::vector<float>
的精確匹配,而創建std::vector<std::vector<float>>
是用戶定義的轉換。 這意味着調用void bar( const std::vector<float>& arg )
是更好的匹配,這就是您所看到的。 你需要使用
bar( { { { v } } } );
這樣外面的一組大括號表示std::vector<std::vector<float>>
,中間的一組是std::initializer_list<std::vector<float>>
,最里面的一組是該列表的單個std::vector<float>
元素。
NathanOliver的回答解釋了歧義的原因。
如果您有興趣區分所有這些情況,您應該添加其他重載:
#include <initializer_list>
#include <iostream>
#include <vector>
void bar( std::vector<float> const& arg ) {
std::cout << "BAR: Vector of float, size " << arg.size() << '\n';
}
void bar( std::vector<std::vector<float>> const& arg ) {
std::cout << "BAR: Vector of vectors of float, size " << arg.size() << '\n';
}
void bar( std::initializer_list<float> lst ) {
std::cout << "BAR: Initializer list of float, size " << lst.size() << '\n';
}
void bar( std::initializer_list<std::initializer_list<float>> lst ) {
std::cout << "BAR: Initializer list of initializer list of float, size "
<< lst.size() << '\n';
}
void bar( std::initializer_list<std::vector<float>> lst ) {
std::cout << "BAR: Initializer list of vector of float, size "
<< lst.size() << '\n';
}
int main()
{
bar( { 1,2,3 } ); // -> Initializer list of float
bar( { { 1,2,3 } } ); // -> Initializer list of initializer list of float
std::vector<float> v = { 1,2,3,4,5 };
bar( v ); // -> Vector of float
bar( { v } ); // -> Initializer list of vector of float
}
住在這里。
這是一個非常有趣的list initialization
案例。
bar({1, 2, 3})
bar({{1, 2, 3}})
這是復制列表初始化的情況,這里通過復制列表初始化創建了一個臨時對象,並綁定了常量引用。
要了解bar()
函數調用的工作原理,需要了解列表初始化的工作原理及其重載解析規則。 那么我們一個一個地
std::vector<double> vec = {0, 1};
這也是列表初始化(復制列表初始化), std::vector
具有以下構造函數,
vector( std::initializer_list<T> init,
const Allocator& alloc = Allocator() );
std::initializer_list<T>
構造函數在重載決議中與其他構造函數相比具有最高優先級,並且由於此重載決議選擇它。
還有一個案例,
std::vector<double> vec = {0, 1};
std::vector<double> other = {vec};
這是不同的情況,現在大括號只有一個元素,並且它正是other
變量的類型(即std::vector<double>
),根據list initialization
重載解析規則,更多細節在重載解析的特殊規則中,發生以下情況,
重載解析不選擇std::initializer_list<T>
構造函數,而是選擇復制構造函數,它是完全匹配等級的情況,請在上面提供的鏈接中閱讀。 現在這個案例將有助於了解屏幕后面發生的事情。
讓我們看看下面的代碼,
std::vector<double> other = {{0, 1}};
根據前面的討論,編譯器應該先創建一個臨時的std::vector<double>
,然后移動初始化變量other
但幸運的是編譯器沒有這樣做,而是跟隨,
因此,而不是創建一個臨時std::vector<double>
然后移動初始化變量other
它優化臨時對象並通過調用std::initializer_list
構造函數直接初始化other
變量,並使用{0, 1}
作為構造函數參數。
最后一個案例,
std::vector<std::vector<double>> nestedVec = {{0, 1}};
編譯器將首先創建一個臨時的std::vector<double>
,這意味着上面的表達邏輯上變成std::vector<std::vector<double>> nestedVec = {std::vector<double>{0, 1}};
然后重載決議將選擇std::initializer_list
構造函數。
所以學習是,表達式{{1, 2, 3}}
能夠初始化std::vector<double>
以及std::vector<std::vector<double>>
並在bar()
函數中創建歧義調用,這也意味着應謹慎使用帶有單個元素的std::initializer_list
。
現在要解決歧義,可以執行以下操作,
更改函數調用,
bar({1, 2, 3});
bar({{1, 2, 3}, {}}); //put an empty element.
或使用更多的花括號,如問題所述
bar({{{1, 2, 3}}});
或者首先創建一個變量並將這個變量作為函數參數傳遞,而不是將std::initializer_list
作為函數參數傳遞。
以下代碼將演示我所解釋的內容,
#include <iostream>
#include <vector>
using std::cout;
template <class T>
class Container{
public:
Container(){
cout<< "Default contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
Container(const Container& ){
cout<< "Copy contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
Container(Container&& ){
cout<< "Move contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
Container(const std::initializer_list<T>& ){
cout<< "std::initializer_list contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
};
int main(int , char *[]){
std::cout<<"1 --- ";
Container<double> dObj; //1
cout<< '\n';
std::cout<<"2 --- ";
[[maybe_unused]] Container<double> cObj = {dObj}; //2
cout<< '\n';
std::cout<<"3 --- ";
[[maybe_unused]] Container<double> lObj = {{0, 1}}; //3
cout<< '\n';
std::cout<<"4 --- ";
[[maybe_unused]] Container<Container<double>> nObj = {{0, 1}}; //4
cout<< '\n';
}
輸出:
1 --- Default contructor.
Container<T>::Container() [with T = double]
2 --- Copy contructor.
Container<T>::Container(const Container<T>&) [with T = double]
3 --- std::initializer_list contructor.
Container<T>::Container(const std::initializer_list<_Tp>&) [with T = double]
4 --- std::initializer_list contructor.
Container<T>::Container(const std::initializer_list<_Tp>&) [with T = double]
std::initializer_list contructor.
Container<T>::Container(const std::initializer_list<_Tp>&) [with T = Container<double>]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.