如何将 class 中的字段传递给 C++ 中的函数?
How to pass fields from a class to function in c++?
换句话说:如何将自定义 class 中的各个字段传递给单个函数?
现在详细介绍:
我有一个包含 class 的 std::vector
,例如 CustomClass
,我必须根据一些标准从这个 class 的字段中提取结果,这些标准是这个 class 并以某种方式组合这些数据。
我解决这个问题的第一个方法是使用一个函数,该函数接受 class 的 std::vector
作为参数来提取数据,并且 return a std:map
.此映射中的键是数据组合所依据的标准类型,值是 int
以及来自该向量所有成员的组合数据。
问题在于标准不仅是一个 - 来自此 class 的多个字段可以用作标准(为简单起见,让所有标准都是 std::string
,如果它们是不是 - 我可以使函数模板化)。
现在对我来说最简单的方法是用几乎相同的代码制作几十个函数,每个函数都从这个 class 中提取一个简单的具体字段。然而,更改可能需要对所有几十个功能进行类似的更改,这将是一个令人头疼的维护问题。但是在这个阶段我想不出如何将这个 class...
中的字段传递给单个函数
这是来自 class 的示例代码:
// this is the class with data and criteria
class CustomClass
{
public:
std::string criteria1;
std::string criteria2;
std::string criteria3;
//... and others criteria
int dataToBeCombined;
// other code
};
// this is one of these functions
std::map<std::string, int> getDataByCriteria1(std::vector<CustomClass> aVector)
{
std::map<std::string, int> result;
foreach(CustomClass anObject in aVector)
{
if(result.find(anObject.criteria1)==result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(anObject.criteria1, anObject.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
并且通过类似的方式我应该制作其他应该与 CustomClass::criteria2
、CustomClass::criteria3
等一起工作的函数
我想将这些条件放在一个数组中,并仅将条件的编号传递给此函数,但 class 将被其他人用于其他目的,并且字段必须易于阅读, 所以这不是一个选项(即真实姓名不是 criteria1
、criteria2
等,而是描述性的)。
有人有想法吗?
编辑:有人将我的问题提到了 "C++ same function parameters with different return type",这显然是非常不同的 - 在我的例子中,函数 return 每次都是相同的类型,只是它采用的参数必须是来自class.
您可以使用指向成员的指针。在你的函数中声明一个参数 std::string CustomClass::*pField
,用 &CustomClass::criteriaN
传递它,用 anObject.*pField
.
访问它
查看有关该主题的更多信息:Pointers to data members。
您将其标记为 C++11,因此请使用可变参数模板。
class VariadicTest
{
public:
VariadicTest()
{
std::map<std::string, int> test1 = getDataByCriteria(testValues, criteria1);
std::map<std::string, int> test2 = getDataByCriteria(testValues, criteria2);
std::map<std::string, int> test3 = getDataByCriteria(testValues, criteria1, criteria2);
std::map<std::string, int> test4 = getDataByCriteria(testValues, criteria1, criteria3);
}
private:
std::string criteria1 = { "Hello" };
std::string criteria2 = { "world" };
std::string criteria3 = { "." };
std::vector<CustomClass> testValues = { {"Hello",1}, {"world",2},{ "!",3 } };
template<typename T> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T criteria)
{
std::map<std::string, int> result;
//do whatever is needed here to filter values
for (auto v : values)
{
if (v.identifier == criteria)
{
result[values[0].identifier] = values[0].value;
}
}
return result;
}
template<typename T, typename... Args> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T firstCriteria, Args... args)
{
std::map<std::string, int> result = getDataByCriteria(values, firstCriteria);
std::map<std::string, int> trailer = getDataByCriteria(values, args...);
result.insert(trailer.begin(), trailer.end());
return result;
}
};
您可以使用函数提取字段,例如
std::string extractFiled(const CustomClass &object, int which) {
switch (which) {
case 1:
return object.criteria1;
case 2:
return object.criteria2;
case 3:
return object.criteria3;
default:
return object.criteria1;
}
}
和 getDataByCriteria
添加一个 arg 来指示要使用的文件。
或者你可以只使用宏来实现 getDataByCriteria
.
如果所有 "criteria" 都是同一类型,我看不到一个优雅的解决方案,但您可以 "enumerate" 他们以某种方式使用他们的号码。
例如,您可以通过这种方式在CustomClass
中声明一个模板化的getVal()
方法
template <int I>
const std::string & getVal () const;
并以这种方式(在 class 的主体之外)
逐个编号、逐个标准地实施它们
template <>
const std::string & CustomClass::getVal<1> () const
{ return criteria1; }
template <>
const std::string & CustomClass::getVal<2> () const
{ return criteria2; }
template <>
const std::string & CustomClass::getVal<3> () const
{ return criteria3; }
现在,您可以通过这种方式在模板函数 getDataByCriteria()
中转换 getDataByCriteria1()
template <int I>
std::map<std::string, int> getDataByCriteria (std::vector<CustomClass> aVector)
{
std::map<std::string, int> result;
for (const auto & cc : aVector)
{
if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
然后这样称呼
auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);
--- 编辑:为不同类型标准添加解决方案(仅限 C++14)---
如果 "criteria" 的类型不同,则略有不同。
由于 auto
和 decltype()
.
,解决方案在 C++14 中有效
例如,如果
std::string criteria1;
int criteria2;
long criteria3;
您可以用 auto
声明 getVal()
template <int I>
const auto & getVal () const;
并定义(使用 auto
)getVal()
的所有版本
template <>
const auto & CustomClass::getVal<1> () const
{ return criteria1; }
template <>
const auto & CustomClass::getVal<2> () const
{ return criteria2; }
template <>
const auto & CustomClass::getVal<3> () const
{ return criteria3; }
并结合auto
和decltype()
,可以这样修改getDataByCriteria()
template <int I>
auto getDataByCriteria (std::vector<CustomClass> aVector)
{
std::map<decltype(aVector[0].getVal<I>()), int> result;
for (const auto & cc : aVector)
{
if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
函数的使用保持不变(再次感谢auto
)
auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);
p.s.: 注意:代码未测试
p.s.2 : 对不起我的英语不好
您没有指定在满足条件的各种条件下要完成的实际操作,因此很难说它们实际可以组合多少。
这是使用 STL 的 std::accumulate()
以及一些附加功能的可能解决方案。这个例子是用 Visual Studio 2015.
编译的
如果大多数功能可以组合成一个相当小的累积函数,那么这种方法就有意义,因为大多数条件都以相同的方式处理。或者您可以让 accumulate_op()
函数在处理一般情况本身时针对特定情况调用其他函数。
您可以以此为开端并进行适当的修改。
一个这样的修改可能是摆脱使用 std::map
来维护状态。由于使用这种方法,您将根据条件遍历 std::vector
进行累加,我不确定您是否需要使用 std::map
来记住任何内容,如果您正在累加的话。
// map_fold.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <numeric>
// this is the class with data and criteria
class CustomClass
{
public:
CustomClass() : dataToBeCombined(0) {}
std::string criteria1;
std::string criteria2;
std::string criteria3;
//... and others criteria
int dataToBeCombined;
// other code
};
// This is the class that will contain the results as we accumulate across the
// vector of CustomClass items.
class Criteria_Result {
public:
Criteria_Result() : dataToBeCombined(0) {}
CustomClass myCriteria;
std::map<std::string, int> result1;
std::map<std::string, int> result2;
std::map<std::string, int> result3;
int dataToBeCombined;
};
// This is the accumulation function we provide to std::accumulate().
// This function will build our results.
class accumulate_op {
public:
Criteria_Result * operator ()(Criteria_Result * x, CustomClass &item);
};
Criteria_Result * accumulate_op::operator ()(Criteria_Result *result, CustomClass &item)
{
if (!result->myCriteria.criteria1.empty() && !item.criteria1.empty()) {
std::map<std::string, int>::iterator it1 = result->result1.find(item.criteria1);
if (it1 == result->result1.end()) // if such of key doesn't exists
{
result->result1.insert(std::make_pair(item.criteria1, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it1->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
if (!result->myCriteria.criteria2.empty() && !item.criteria2.empty()) {
std::map<std::string, int>::iterator it2 = result->result2.find(item.criteria2);
if (it2 == result->result2.end()) // if such of key doesn't exists
{
result->result2.insert(std::make_pair(item.criteria2, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it2->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
if (!result->myCriteria.criteria3.empty() && !item.criteria3.empty()) {
std::map<std::string, int>::iterator it3 = result->result3.find(item.criteria3);
if (it3 == result->result3.end()) // if such of key doesn't exists
{
result->result3.insert(std::make_pair(item.criteria3, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it3->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
return result;
}
int main()
{
Criteria_Result result;
std::vector<CustomClass> aVector;
// set up the criteria for the search
result.myCriteria.criteria1 = "string1";
result.myCriteria.criteria2 = "string2";
for (int i = 0; i < 10; i++) {
CustomClass xx;
xx.dataToBeCombined = i;
if (i % 2) {
xx.criteria1 = "string";
}
else {
xx.criteria1 = "string1";
}
if (i % 3) {
xx.criteria2 = "string";
}
else {
xx.criteria2 = "string2";
}
aVector.push_back (xx);
}
// fold the vector into our results.
std::accumulate (aVector.begin(), aVector.end(), &result, accumulate_op());
std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;
std::cout << " result1 list " << std::endl;
for (auto jj : result.result1) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result2 list " << std::endl;
for (auto jj : result.result2) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result3 list " << std::endl;
for (auto jj : result.result3) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " Trial two \n\n" << std::endl;
result.myCriteria.criteria2 = "";
result.result1.clear();
result.result2.clear();
result.result3.clear();
result.dataToBeCombined = 0;
// fold the vector into our results.
std::accumulate(aVector.begin(), aVector.end(), &result, accumulate_op());
std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;
std::cout << " result1 list " << std::endl;
for (auto jj : result.result1) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result2 list " << std::endl;
for (auto jj : result.result2) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result3 list " << std::endl;
for (auto jj : result.result3) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
return 0;
}
这会产生如下输出:
Total Data to be combined 90
result1 list
string 25
string1 20
result2 list
string 27
string2 18
result3 list
Trial two
Total Data to be combined 45
result1 list
string 25
string1 20
result2 list
result3 list
换句话说:如何将自定义 class 中的各个字段传递给单个函数?
现在详细介绍:
我有一个包含 class 的 std::vector
,例如 CustomClass
,我必须根据一些标准从这个 class 的字段中提取结果,这些标准是这个 class 并以某种方式组合这些数据。
我解决这个问题的第一个方法是使用一个函数,该函数接受 class 的 std::vector
作为参数来提取数据,并且 return a std:map
.此映射中的键是数据组合所依据的标准类型,值是 int
以及来自该向量所有成员的组合数据。
问题在于标准不仅是一个 - 来自此 class 的多个字段可以用作标准(为简单起见,让所有标准都是 std::string
,如果它们是不是 - 我可以使函数模板化)。
现在对我来说最简单的方法是用几乎相同的代码制作几十个函数,每个函数都从这个 class 中提取一个简单的具体字段。然而,更改可能需要对所有几十个功能进行类似的更改,这将是一个令人头疼的维护问题。但是在这个阶段我想不出如何将这个 class...
中的字段传递给单个函数这是来自 class 的示例代码:
// this is the class with data and criteria
class CustomClass
{
public:
std::string criteria1;
std::string criteria2;
std::string criteria3;
//... and others criteria
int dataToBeCombined;
// other code
};
// this is one of these functions
std::map<std::string, int> getDataByCriteria1(std::vector<CustomClass> aVector)
{
std::map<std::string, int> result;
foreach(CustomClass anObject in aVector)
{
if(result.find(anObject.criteria1)==result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(anObject.criteria1, anObject.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
并且通过类似的方式我应该制作其他应该与 CustomClass::criteria2
、CustomClass::criteria3
等一起工作的函数
我想将这些条件放在一个数组中,并仅将条件的编号传递给此函数,但 class 将被其他人用于其他目的,并且字段必须易于阅读, 所以这不是一个选项(即真实姓名不是 criteria1
、criteria2
等,而是描述性的)。
有人有想法吗?
编辑:有人将我的问题提到了 "C++ same function parameters with different return type",这显然是非常不同的 - 在我的例子中,函数 return 每次都是相同的类型,只是它采用的参数必须是来自class.
您可以使用指向成员的指针。在你的函数中声明一个参数 std::string CustomClass::*pField
,用 &CustomClass::criteriaN
传递它,用 anObject.*pField
.
查看有关该主题的更多信息:Pointers to data members。
您将其标记为 C++11,因此请使用可变参数模板。
class VariadicTest
{
public:
VariadicTest()
{
std::map<std::string, int> test1 = getDataByCriteria(testValues, criteria1);
std::map<std::string, int> test2 = getDataByCriteria(testValues, criteria2);
std::map<std::string, int> test3 = getDataByCriteria(testValues, criteria1, criteria2);
std::map<std::string, int> test4 = getDataByCriteria(testValues, criteria1, criteria3);
}
private:
std::string criteria1 = { "Hello" };
std::string criteria2 = { "world" };
std::string criteria3 = { "." };
std::vector<CustomClass> testValues = { {"Hello",1}, {"world",2},{ "!",3 } };
template<typename T> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T criteria)
{
std::map<std::string, int> result;
//do whatever is needed here to filter values
for (auto v : values)
{
if (v.identifier == criteria)
{
result[values[0].identifier] = values[0].value;
}
}
return result;
}
template<typename T, typename... Args> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T firstCriteria, Args... args)
{
std::map<std::string, int> result = getDataByCriteria(values, firstCriteria);
std::map<std::string, int> trailer = getDataByCriteria(values, args...);
result.insert(trailer.begin(), trailer.end());
return result;
}
};
您可以使用函数提取字段,例如
std::string extractFiled(const CustomClass &object, int which) {
switch (which) {
case 1:
return object.criteria1;
case 2:
return object.criteria2;
case 3:
return object.criteria3;
default:
return object.criteria1;
}
}
和 getDataByCriteria
添加一个 arg 来指示要使用的文件。
或者你可以只使用宏来实现 getDataByCriteria
.
如果所有 "criteria" 都是同一类型,我看不到一个优雅的解决方案,但您可以 "enumerate" 他们以某种方式使用他们的号码。
例如,您可以通过这种方式在CustomClass
中声明一个模板化的getVal()
方法
template <int I>
const std::string & getVal () const;
并以这种方式(在 class 的主体之外)
逐个编号、逐个标准地实施它们template <>
const std::string & CustomClass::getVal<1> () const
{ return criteria1; }
template <>
const std::string & CustomClass::getVal<2> () const
{ return criteria2; }
template <>
const std::string & CustomClass::getVal<3> () const
{ return criteria3; }
现在,您可以通过这种方式在模板函数 getDataByCriteria()
中转换 getDataByCriteria1()
template <int I>
std::map<std::string, int> getDataByCriteria (std::vector<CustomClass> aVector)
{
std::map<std::string, int> result;
for (const auto & cc : aVector)
{
if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
然后这样称呼
auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);
--- 编辑:为不同类型标准添加解决方案(仅限 C++14)---
如果 "criteria" 的类型不同,则略有不同。
由于 auto
和 decltype()
.
例如,如果
std::string criteria1;
int criteria2;
long criteria3;
您可以用 auto
getVal()
template <int I>
const auto & getVal () const;
并定义(使用 auto
)getVal()
template <>
const auto & CustomClass::getVal<1> () const
{ return criteria1; }
template <>
const auto & CustomClass::getVal<2> () const
{ return criteria2; }
template <>
const auto & CustomClass::getVal<3> () const
{ return criteria3; }
并结合auto
和decltype()
,可以这样修改getDataByCriteria()
template <int I>
auto getDataByCriteria (std::vector<CustomClass> aVector)
{
std::map<decltype(aVector[0].getVal<I>()), int> result;
for (const auto & cc : aVector)
{
if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
函数的使用保持不变(再次感谢auto
)
auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);
p.s.: 注意:代码未测试
p.s.2 : 对不起我的英语不好
您没有指定在满足条件的各种条件下要完成的实际操作,因此很难说它们实际可以组合多少。
这是使用 STL 的 std::accumulate()
以及一些附加功能的可能解决方案。这个例子是用 Visual Studio 2015.
如果大多数功能可以组合成一个相当小的累积函数,那么这种方法就有意义,因为大多数条件都以相同的方式处理。或者您可以让 accumulate_op()
函数在处理一般情况本身时针对特定情况调用其他函数。
您可以以此为开端并进行适当的修改。
一个这样的修改可能是摆脱使用 std::map
来维护状态。由于使用这种方法,您将根据条件遍历 std::vector
进行累加,我不确定您是否需要使用 std::map
来记住任何内容,如果您正在累加的话。
// map_fold.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <numeric>
// this is the class with data and criteria
class CustomClass
{
public:
CustomClass() : dataToBeCombined(0) {}
std::string criteria1;
std::string criteria2;
std::string criteria3;
//... and others criteria
int dataToBeCombined;
// other code
};
// This is the class that will contain the results as we accumulate across the
// vector of CustomClass items.
class Criteria_Result {
public:
Criteria_Result() : dataToBeCombined(0) {}
CustomClass myCriteria;
std::map<std::string, int> result1;
std::map<std::string, int> result2;
std::map<std::string, int> result3;
int dataToBeCombined;
};
// This is the accumulation function we provide to std::accumulate().
// This function will build our results.
class accumulate_op {
public:
Criteria_Result * operator ()(Criteria_Result * x, CustomClass &item);
};
Criteria_Result * accumulate_op::operator ()(Criteria_Result *result, CustomClass &item)
{
if (!result->myCriteria.criteria1.empty() && !item.criteria1.empty()) {
std::map<std::string, int>::iterator it1 = result->result1.find(item.criteria1);
if (it1 == result->result1.end()) // if such of key doesn't exists
{
result->result1.insert(std::make_pair(item.criteria1, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it1->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
if (!result->myCriteria.criteria2.empty() && !item.criteria2.empty()) {
std::map<std::string, int>::iterator it2 = result->result2.find(item.criteria2);
if (it2 == result->result2.end()) // if such of key doesn't exists
{
result->result2.insert(std::make_pair(item.criteria2, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it2->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
if (!result->myCriteria.criteria3.empty() && !item.criteria3.empty()) {
std::map<std::string, int>::iterator it3 = result->result3.find(item.criteria3);
if (it3 == result->result3.end()) // if such of key doesn't exists
{
result->result3.insert(std::make_pair(item.criteria3, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it3->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
return result;
}
int main()
{
Criteria_Result result;
std::vector<CustomClass> aVector;
// set up the criteria for the search
result.myCriteria.criteria1 = "string1";
result.myCriteria.criteria2 = "string2";
for (int i = 0; i < 10; i++) {
CustomClass xx;
xx.dataToBeCombined = i;
if (i % 2) {
xx.criteria1 = "string";
}
else {
xx.criteria1 = "string1";
}
if (i % 3) {
xx.criteria2 = "string";
}
else {
xx.criteria2 = "string2";
}
aVector.push_back (xx);
}
// fold the vector into our results.
std::accumulate (aVector.begin(), aVector.end(), &result, accumulate_op());
std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;
std::cout << " result1 list " << std::endl;
for (auto jj : result.result1) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result2 list " << std::endl;
for (auto jj : result.result2) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result3 list " << std::endl;
for (auto jj : result.result3) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " Trial two \n\n" << std::endl;
result.myCriteria.criteria2 = "";
result.result1.clear();
result.result2.clear();
result.result3.clear();
result.dataToBeCombined = 0;
// fold the vector into our results.
std::accumulate(aVector.begin(), aVector.end(), &result, accumulate_op());
std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;
std::cout << " result1 list " << std::endl;
for (auto jj : result.result1) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result2 list " << std::endl;
for (auto jj : result.result2) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result3 list " << std::endl;
for (auto jj : result.result3) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
return 0;
}
这会产生如下输出:
Total Data to be combined 90
result1 list
string 25
string1 20
result2 list
string 27
string2 18
result3 list
Trial two
Total Data to be combined 45
result1 list
string 25
string1 20
result2 list
result3 list