指向类数据成员“::*”的指针

Pointer to class data member "::*"

提问人:Ashwin Nanjappa 提问时间:3/22/2009 最后编辑:L. F.Ashwin Nanjappa 更新时间:6/20/2023 访问量:213427

问:

我遇到了这个奇怪的代码片段,它编译得很好:

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;
    return 0;
}

为什么 C++ 有这个指向类的非静态数据成员的指针?这个奇怪的指针在实际代码中有什么用?

C 指针 C++-FAQ

评论

0赞 HostileFork says dont trust SE 7/12/2012
这是我找到它的地方,也让我感到困惑......但现在有意义了:stackoverflow.com/a/982941/211160
4赞 Kai Petzke 2/18/2021
指向成员的指针是 C++ 的类型安全替代 C 中相当不安全的构造。它们都返回信息,即某个字段或某个字段所在的位置。offsetof()classstruct

答:

42赞 peterchen 3/22/2009 #1

以后可以在任何实例上访问此成员:

int main()
{    
  int Car::*pSpeed = &Car::speed;    
  Car myCar;
  Car yourCar;

  int mySpeed = myCar.*pSpeed;
  int yourSpeed = yourCar.*pSpeed;

  assert(mySpeed > yourSpeed); // ;-)

  return 0;
}

请注意,您确实需要一个实例来调用它,因此它不像委托那样工作。
它很少使用,我这些年来可能需要它一两次。

通常使用接口(即 C++ 中的纯基类)是更好的设计选择。

评论

0赞 thecoshman 10/7/2010
但这肯定只是不好的做法吗?应该做类似 youcar.setspeed(mycar.getpspeed) 的事情
13赞 peterchen 10/12/2010
@thecoshman:完全取决于 - 将数据成员隐藏在 set/get 方法后面不是封装,而只是挤奶女工对接口抽象的尝试。在许多情况下,对公共成员进行“非规范化”是一个合理的选择。但这种讨论可能超出了注释功能的范围。
5赞 johnbakers 5/21/2013
+1 指出,如果我理解正确的话,这是指向任何实例成员的指针,而不是指向一个实例的特定值的指针,这是我完全缺少的部分。
0赞 peterchen 5/23/2013
@Fellowshee 您确实正确理解:)(在回答中强调了这一点)。
27赞 AHelps 3/22/2009 #2

IBM 提供了更多关于如何使用它的文档。简而言之,您将指针用作类的偏移量。除了它们引用的类之外,您不能使用这些指针,因此:

  int Car::*pSpeed = &Car::speed;
  Car mycar;
  mycar.*pSpeed = 65;

这似乎有点晦涩难懂,但一个可能的应用是,如果您尝试编写代码以将泛型数据反序列化为许多不同的对象类型,并且您的代码需要处理它完全不知道的对象类型(例如,您的代码位于库中,并且您反序列化的对象是由库的用户创建的)。成员指针为您提供了一种通用的、半清晰的方式来引用单个数据成员偏移量,而不必像 C 结构那样求助于无类型 void * 技巧。

评论

0赞 Ashwin Nanjappa 3/22/2009
您能否分享一个代码片段示例,其中此构造很有用?谢谢。
2赞 Dan 8/11/2009
我目前正在做很多这样的工作,因为做了一些 DCOM 工作并使用托管资源类,这涉及在每次调用之前做一些工作,并使用数据成员作为内部表示发送到 com,加上模板化,使许多样板代码小得多
251赞 anon 3/22/2009 #3

它是一个“指向成员的指针” - 以下代码说明了它的用法:

#include <iostream>
using namespace std;

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;

    Car c1;
    c1.speed = 1;       // direct access
    cout << "speed is " << c1.speed << endl;
    c1.*pSpeed = 2;     // access via pointer to member
    cout << "speed is " << c1.speed << endl;
    return 0;
}

至于你为什么要这样做,它给了你另一个层次的间接性,可以解决一些棘手的问题。但老实说,我从来没有在自己的代码中使用过它们。

编辑:我想不出一个令人信服的指针指向成员数据的用途。指向成员函数的指针可以在可插拔架构中使用,但再次在狭小的空间中生成一个示例让我感到沮丧。以下是我最好的(未经测试的)尝试 - 一个 Apply 函数,该函数将在将用户选择的成员函数应用于对象之前进行一些预处理和后处理:

void Apply( SomeClass * c, void (SomeClass::*func)() ) {
    // do hefty pre-call processing
    (c->*func)();  // call user specified function
    // do hefty post-call processing
}

括号是必需的,因为运算符的优先级低于函数调用运算符。c->*func->*

评论

4赞 Ashwin Nanjappa 3/22/2009
您能举例说明一个棘手的情况吗?谢谢。
0赞 Mike DeSimone 4/14/2011
我有一个在另一个 SO 答案的 Traits 类中使用指向成员的指针的例子。
0赞 Benji XVI 12/29/2012
例如,为某些基于事件的系统编写“回调”类型的类。例如,CEGUI 的 UI 事件订阅系统采用模板化回调,该回调存储指向所选成员函数的指针,以便您可以指定处理事件的方法。
3赞 alveko 6/7/2013
在此代码中,模板函数中有一个非常酷的指向数据成员的指针用法示例
5赞 Alexey Biryukov 4/9/2015
我最近在序列化框架中使用了指向数据成员的指针。静态封送处理器对象使用包含指向可序列化数据成员的指针的包装器列表进行初始化。此代码的早期原型。
-1赞 Andrew Jaffe 3/22/2009 #4

我认为只有当成员数据非常大(例如,另一个相当大的类的对象)并且您有一些外部例程仅适用于对该类对象的引用时,您才想这样做。您不想复制成员对象,因此您可以传递它。

2赞 Troubadour 3/22/2009 #5

我使用它的一种方式是,如果我有两个如何在类中做某事的实现,并且我想在运行时选择一个,而不必不断地经历 if 语句,即

class Algorithm
{
public:
    Algorithm() : m_impFn( &Algorithm::implementationA ) {}
    void frequentlyCalled()
    {
        // Avoid if ( using A ) else if ( using B ) type of thing
        (this->*m_impFn)();
    }
private:
    void implementationA() { /*...*/ }
    void implementationB() { /*...*/ }

    typedef void ( Algorithm::*IMP_FN ) ();
    IMP_FN m_impFn;
};

显然,这只有在您觉得代码被锤击得足够多以至于 if 语句减慢了完成速度时才真正有用,例如。深藏于某处一些密集算法的深处。我仍然认为它比 if 语句更优雅,即使在它没有实际用途的情况下也是如此,但这只是我的看法。

评论

0赞 shycha 4/10/2019
基本上,您可以使用抽象类和两个派生类(例如 和 .在这种情况下,两种算法是分开的,并确保独立测试。AlgorithmAlgorithmAAlgorithmB
21赞 Alex B 3/22/2009 #6

它使得以统一的方式绑定成员变量和函数成为可能。下面是 Car 类的示例。更常见的用法是绑定,在地图上使用 STL 算法和 Boost 时。std::pair::first::second

#include <list>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>


class Car {
public:
    Car(int s): speed(s) {}
    void drive() {
        std::cout << "Driving at " << speed << " km/h" << std::endl;
    }
    int speed;
};

int main() {

    using namespace std;
    using namespace boost::lambda;

    list<Car> l;
    l.push_back(Car(10));
    l.push_back(Car(140));
    l.push_back(Car(130));
    l.push_back(Car(60));

    // Speeding cars
    list<Car> s;

    // Binding a value to a member variable.
    // Find all cars with speed over 60 km/h.
    remove_copy_if(l.begin(), l.end(),
                   back_inserter(s),
                   bind(&Car::speed, _1) <= 60);

    // Binding a value to a member function.
    // Call a function on each car.
    for_each(s.begin(), s.end(), bind(&Car::drive, _1));

    return 0;
}
70赞 Johannes Schaub - litb 3/23/2009 #7

另一个应用是侵入式列表。元素类型可以告诉列表它的下一个/上一个指针是什么。因此,该列表不使用硬编码名称,但仍可以使用现有指针:

// say this is some existing structure. And we want to use
// a list. We can tell it that the next pointer
// is apple::next.
struct apple {
    int data;
    apple * next;
};

// simple example of a minimal intrusive list. Could specify the
// member pointer as template argument too, if we wanted:
// template<typename E, E *E::*next_ptr>
template<typename E>
struct List {
    List(E *E::*next_ptr):head(0), next_ptr(next_ptr) { }

    void add(E &e) {
        // access its next pointer by the member pointer
        e.*next_ptr = head;
        head = &e;
    }

    E * head;
    E *E::*next_ptr;
};

int main() {
    List<apple> lst(&apple::next);

    apple a;
    lst.add(a);
}

评论

0赞 eeeeaaii 8/26/2011
如果这真的是一个链表,你难道不想要这样的东西吗: void add(E* e) { e->*next_ptr = head; head = e; } ??
5赞 Johannes Schaub - litb 8/26/2011
@eee 我建议您阅读有关参考参数的信息。我所做的基本上等同于你所做的。
0赞 Alcott 8/14/2012
+1 为您的代码示例,但我认为没有必要使用指向成员的指针,还有其他例子吗?
10赞 icktoofay 5/19/2013
@Alcott:您可以将其应用于下一个指针未命名的其他类似链接列表的结构。next
11赞 Functastic 3/23/2009 #8

您可以使用指向(同构)成员数据的指针数组来启用双重命名成员(即 x.data)和数组下标(即 x[idx])接口。

#include <cassert>
#include <cstddef>

struct vector3 {
    float x;
    float y;
    float z;

    float& operator[](std::size_t idx) {
        static float vector3::*component[3] = {
            &vector3::x, &vector3::y, &vector3::z
        };
        return this->*component[idx];
    }
};

int main()
{
    vector3 v = { 0.0f, 1.0f, 2.0f };

    assert(&v[0] == &v.x);
    assert(&v[1] == &v.y);
    assert(&v[2] == &v.z);

    for (std::size_t i = 0; i < 3; ++i) {
        v[i] += 1.0f;
    }

    assert(v.x == 1.0f);
    assert(v.y == 2.0f);
    assert(v.z == 3.0f);

    return 0;
}

评论

0赞 Dwayne Robinson 4/21/2015
我更经常看到它使用包含数组字段 v[3] 的匿名联合来实现,因为它避免了间接,但仍然很聪明,并且可能对非连续字段有用。
3赞 underscore_d 9/8/2016
@DwayneRobinson但是,标准不允许以这种方式使用双关语,因为它会调用多种形式的未定义行为......而这个答案是可以的。union
0赞 tobi_s 5/21/2020
这是一个简洁的例子,但 operator[] 可以在没有指向组件的指针的情况下重写:也就是说,指向组件的指针似乎除了混淆之外没有任何作用。float *component[] = { &x, &y, &z }; return *component[idx];
50赞 Tom 11/2/2010 #9

这是我现在正在研究的一个真实世界的例子,来自信号处理/控制系统:

假设您有一些结构来表示您正在收集的数据:

struct Sample {
    time_t time;
    double value1;
    double value2;
    double value3;
};

现在假设你把它们塞进一个向量中:

std::vector<Sample> samples;
... fill the vector ...

现在假设您要在一系列样本中计算其中一个变量的某个函数(例如平均值),并且您希望将此均值计算分解为一个函数。指向成员的指针使它变得容易:

double Mean(std::vector<Sample>::const_iterator begin, 
    std::vector<Sample>::const_iterator end,
    double Sample::* var)
{
    float mean = 0;
    int samples = 0;
    for(; begin != end; begin++) {
        const Sample& s = *begin;
        mean += s.*var;
        samples++;
    }
    mean /= samples;
    return mean;
}

...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);

注意:编辑于 2016/08/05 以获得更简洁的模板函数方法

当然,您可以对其进行模板化,以计算任何正向迭代器和任何支持自身加法和除以size_t的值类型的平均值:

template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
    using T = typename std::iterator_traits<Titer>::value_type;
    S sum = 0;
    size_t samples = 0;
    for( ; begin != end ; ++begin ) {
        const T& s = *begin;
        sum += s.*var;
        samples++;
    }
    return sum / samples;
}

struct Sample {
    double x;
}

std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);

编辑 - 上面的代码对性能有影响

您应该注意到,正如我很快发现的那样,上面的代码对性能有一些严重的影响。总而言之,如果要计算时间序列的汇总统计量,或计算FFT等,则应将每个变量的值连续存储在内存中。否则,循环访问序列将导致检索到的每个值的缓存未命中。

请考虑以下代码的性能:

struct Sample {
  float w, x, y, z;
};

std::vector<Sample> series = ...;

float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
  sum += *it.x;
  samples++;
}
float mean = sum / samples;

在许多体系结构上,一个实例将填充缓存行。因此,在循环的每次迭代中,都会从内存中提取一个样本到缓存中。缓存行中的 4 个字节将被使用,其余的将被丢弃,下一次迭代将导致另一个缓存未命中、内存访问等。Sample

这样做要好得多:

struct Samples {
  std::vector<float> w, x, y, z;
};

Samples series = ...;

float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
  sum += *it;
  samples++;
}
float mean = sum / samples;

现在,当第一个 x 值从内存中加载时,接下来的三个值也将加载到缓存中(假设合适的对齐方式),这意味着您不需要为接下来的三次迭代加载任何值。

通过在 SSE2 架构上使用 SIMD 指令,可以进一步改进上述算法。但是,如果这些值在内存中都是连续的,并且您可以使用单个指令将四个样本加载在一起(在以后的 SSE 版本中会更多),则这些效果会更好

YMMV - 设计适合您的算法的数据结构。

评论

0赞 Nicu Stiurca 3/26/2013
这太好了。我即将实现非常相似的东西,现在我不必弄清楚奇怪的语法!谢谢!
2赞 Eyal 11/7/2019
这是最好的答案。零件是关键!double Sample::*
3赞 lnksz 9/23/2021
这通常称为 AoS 与 SoA:en.wikipedia.org/wiki/AoS_and_SoA
125赞 John McFarlane 4/30/2011 #10

这是我能想到的最简单的例子,它传达了与此功能相关的罕见情况:

#include <iostream>

class bowl {
public:
    int apples;
    int oranges;
};

int count_fruit(bowl * begin, bowl * end, int bowl::*fruit)
{
    int count = 0;
    for (bowl * iterator = begin; iterator != end; ++ iterator)
        count += iterator->*fruit;
    return count;
}

int main()
{
    bowl bowls[2] = {
        { 1, 2 },
        { 3, 5 }
    };
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::apples) << " apples\n";
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::oranges) << " oranges\n";
    return 0;
}

这里需要注意的是传入count_fruit的指针。这样就不必编写单独的 count_apples 和 count_oranges 函数。

评论

3赞 Dan Nissenbaum 3/30/2014
不应该是吗? 并且没有指向任何东西。&bowls.apples&bowls.oranges&bowl::apples&bowl::oranges
27赞 John McFarlane 3/31/2014
&bowl::apples并且不要指向对象的成员;它们指向的成员。在指向某些内容之前,它们需要与指向实际对象的指针组合在一起。这种组合是通过操作员实现的。&bowl::oranges->*
0赞 fabian 4/13/2021
非常感谢您提供这个非常说明性的例子!尽管如此,我想我仍然没有完全理解这句话:.这个表达式的类型和参数名称是什么?int bowl::*fruit
1赞 John McFarlane 4/13/2021
@fabian YW!参数名称为 。它的类型说,“我指着一个是班级的成员。在后台,它通常实现为与类开头的偏移量,即 0 个字节或 4 个字节。下面是一个更简单的示例,其中包含一个仅递增成员的函数。 指定该成员变量中作为字节偏移量的位置。这是作为偏移量传递或传入的调用代码fruitintbowlapplesorangesfruitb04
1赞 Parker Coates 1/12/2023
很好的答案。它唯一缺少的是指出,使用别名可以使指向成员的指针更易于使用。如果您添加了最后一个参数,那么最后一个参数可能是 ,这对大多数人来说应该读起来更自然。我建议每当使用指向函数、方法或数据成员的指针时都这样做。using fruit_member_ptr = int bowl::*;count_fruitfruit_member_ptr fruit
0赞 prestokeys 11/19/2015 #11

下面是一个示例,其中指向数据成员的指针可能很有用:

#include <iostream>
#include <list>
#include <string>

template <typename Container, typename T, typename DataPtr>
typename Container::value_type searchByDataMember (const Container& container, const T& t, DataPtr ptr) {
    for (const typename Container::value_type& x : container) {
        if (x->*ptr == t)
            return x;
    }
    return typename Container::value_type{};
}

struct Object {
    int ID, value;
    std::string name;
    Object (int i, int v, const std::string& n) : ID(i), value(v), name(n) {}
};

std::list<Object*> objects { new Object(5,6,"Sam"), new Object(11,7,"Mark"), new Object(9,12,"Rob"),
    new Object(2,11,"Tom"), new Object(15,16,"John") };

int main() {
    const Object* object = searchByDataMember (objects, 11, &Object::value);
    std::cout << object->name << '\n';  // Tom
}
0赞 inetknght 4/12/2016 #12

假设你有一个结构。该结构的内部是 * 某种名称 * 两个类型相同但含义不同的变量

struct foo {
    std::string a;
    std::string b;
};

好了,现在假设你在一个容器里有一堆 s:foo

// key: some sort of name, value: a foo instance
std::map<std::string, foo> container;

好的,现在假设您从不同的源加载数据,但数据以相同的方式呈现(例如,您需要相同的解析方法)。

你可以做这样的事情:

void readDataFromText(std::istream & input, std::map<std::string, foo> & container, std::string foo::*storage) {
    std::string line, name, value;

    // while lines are successfully retrieved
    while (std::getline(input, line)) {
        std::stringstream linestr(line);
        if ( line.empty() ) {
            continue;
        }

        // retrieve name and value
        linestr >> name >> value;

        // store value into correct storage, whichever one is correct
        container[name].*storage = value;
    }
}

std::map<std::string, foo> readValues() {
    std::map<std::string, foo> foos;

    std::ifstream a("input-a");
    readDataFromText(a, foos, &foo::a);
    std::ifstream b("input-b");
    readDataFromText(b, foos, &foo::b);
    return foos;
}

此时,调用将返回一个包含“input-a”和“input-b”的容器;所有键都将存在,并且 foo 具有 a 或 b 或两者兼而有之。readValues()

0赞 Dragonly 12/25/2016 #13

为了给@anon和@Oktalist的答案添加一些用例,这里有一个关于指针到成员函数和指针到成员数据的很好的阅读材料。

https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-ptmf4.pdf

评论

1赞 phuclv 9/3/2019
链接已失效。这就是为什么这里不需要仅链接的答案。至少总结一下链接的内容,否则当链接腐烂时,你的答案就会失效
2赞 Arijit Dey 6/28/2018 #14

指向类的指针不是真正的指针;类是一种逻辑构造,在内存中没有物理存在,但是,当您构造指向类成员的指针时,它会向可以找到该成员的类的对象提供偏移量;这给出了一个重要的结论:由于静态成员不与任何对象相关联,因此指向成员的指针不能指向静态成员(数据或函数) 请考虑以下几点:

class x {
public:
    int val;
    x(int i) { val = i;}

    int get_val() { return val; }
    int d_val(int i) {return i+i; }
};

int main() {
    int (x::* data) = &x::val;               //pointer to data member
    int (x::* func)(int) = &x::d_val;        //pointer to function member

    x ob1(1), ob2(2);

    cout <<ob1.*data;
    cout <<ob2.*data;

    cout <<(ob1.*func)(ob1.*data);
    cout <<(ob2.*func)(ob2.*data);


    return 0;
}

来源:完整参考 C++ - Herbert Schildt 第 4 版

-1赞 benb 9/20/2020 #15

指向成员的指针的实际示例可能是 std::shared_ptr 的更窄的别名构造函数:

template <typename T>
template <typename U>
shared_ptr<T>::shared_ptr(const shared_ptr<U>, T U::*member);

该构造函数的用途

假设你有一个结构体 foo:

struct foo {
    int ival;
    float fval;
};

如果你给一个 foo shared_ptr,那么你可以使用该构造函数检索shared_ptr到它的成员 ival 或 fval:

auto foo_shared = std::make_shared<foo>();
auto ival_shared = std::shared_ptr<int>(foo_shared, &foo::ival);

如果要将指针 foo_shared->ival 传递给某个需要shared_ptr的函数,这将很有用

https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

评论

0赞 Quuxplusone 1/14/2021
是的,但会做同样的工作,使用现有的标准库,而不使用指向成员的指针。所以这个答案让 OP 仍然在问“但我为什么要这样做?auto ival_shared = std::shared_ptr<int>(foo_shared, &foo_shared->ival);
0赞 Kai Petzke 2/20/2021 #16

指向成员的指针是 C++ 的 C 类型安全等效项,定义如下: 两者都返回信息,其中某个字段位于 或 中。虽然在 C++ 中也可以与某些足够简单的类一起使用,但在一般情况下,它失败得很惨,尤其是虚拟基类。因此,指向成员的指针被添加到标准中。它们还提供了更简单的语法来引用实际字段:offsetof()stddef.hclassstructoffsetof()

struct C { int a; int b; } c;
int C::* intptr = &C::a;       // or &C::b, depending on the field wanted
c.*intptr += 1;

比以下方法容易得多:

struct C { int a; int b; } c;
int intoffset = offsetof(struct C, a);
* (int *) (((char *) (void *) &c) + intoffset) += 1;

至于为什么要使用(或指向成员的指针),stackoverflow 上的其他地方都有很好的答案。下面举个例子:C offsetof 宏是如何工作的?offsetof()

0赞 Spyros Tsimboulis 7/22/2021 #17

使用指向 Member 的指针,我们可以编写这样的通用代码

template<typename T, typename U>
struct alpha{
   T U::*p_some_member;
};

struct beta{
   int foo;
};

int main()
{

   beta b{};

   alpha<int, beta> a{&beta::foo};

   b.*(a.p_some_member) = 4;

   return 0;
}
0赞 mada 11/1/2021 #18

我喜欢 和 运算符:*&

struct X 
{ 
    int a {0}; 
    int *ptr {NULL};

    int &fa() { return a; }
    int *&fptr() { return ptr; }
};

int main(void) 
{
    X x;
    int X::*p1 = &X::a;     // pointer-to-member 'int X::a'. Type of p1 = 'int X::*'
    x.*p1 = 10;

    int *X::*p2 = &X::ptr;  // pointer-to-member-pointer 'int *X::ptr'. Type of p2 = 'int *X::*' 
    x.*p2 = nullptr;
    X *xx;
    xx->*p2 = nullptr;

    int& (X::*p3)() = X::fa; // pointer-to-member-function 'X::fa'. Type of p3 = 'int &(X::*)()'
    (x.*p3)() = 20; 
    (xx->*p3)() = 30;

    int *&(X::*p4)() = X::fptr;  // pointer-to-member-function 'X::fptr'. Type of p4 = 'int *&(X::*)()'
    (x.*p4)() = nullptr; 
    (xx->*p4)() = nullptr;
}

事实上,只要成员是公开的或静态的,一切都是正确的

0赞 ahcox 6/20/2023 #19

下面是一个示例,使用此语法编写模板算法,用于从该结构的数组中复制结构的单个成员,并将其放置在成员类型的数组中:

/// @param member A pointer to the struct/class member like `&MyClass::name_of_member`.
/// Note this is a pointer to the member in the class, not a pointer to an instance of it
/// in an instance of the the class.
template <typename InputIterator, typename OutputIterator, typename MemberType, typename StructType>
void extract_member(const InputIterator input, const InputIterator end, OutputIterator output, MemberType StructType::*member) {
    std::transform(input, end, output, [member](const StructType& obj) {
        return obj.*member;
    });
}

以下是如何使用它:

extract_member(std::begin(struct_container), std::end(struct_container), std::begin(scalar_container), &MyClass::name_of_member);
// (scalar_container is presized or you could use a back inserter)

此语法允许您向泛型帮助程序函数指定要选择的成员。