You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++20/23中如何避免引用类型下构造函数签名被求值

问题描述

我希望当data_type是任意引用类型时,避免property类第一个构造函数的签名被求值——因为指向引用成员的指针是非法的(示例中name/set_name会触发该情况)。我原以为constexpr构造函数在name/set_name场景下不会被求值,但实际并非如此。我尝试过enable_if和requires语句,但都无法阻止签名本身被求值(事后看来这合理,因为需要求值签名来推导class_type和data_type)。请问有没有办法实现我的需求:在同一对象的constexpr上下文中,既能接受类成员并生成getter/setter的lambda,又能接受getter/setter对?

C++ 标准:20 或 23


相关代码

test_property.cpp

#include <string>
#include <tuple>
#include "property.h"

class object {
public:
  int index() const {
    return this->_idx;
  }

  void set_index(int value) {
    this->_idx = value;
  }

  const std::string& name() const {
    return this->_name;
  }

  void set_name(const std::string& value) {
    this->_name = value;
  }

private:
  bool _flag{};
  int _idx{};
  std::string _name{};

  constexpr static auto properties = std::make_tuple(
    property("name", &object::name, &object::set_name),
    property("index", &object::index, &object::set_index),
    property("flag", &object::_flag)
  );
};

int main(int argc, char** argv) {
  object obj;
  return 0;
}

property.h v1(使用enable_if)

#include <type_traits>

template <typename class_type, typename data_type>
class property {
public:
  template <typename = std::enable_if_t<!std::is_reference_v<data_type>>>
  inline constexpr property(const char* name, data_type class_type::*property) {
  }

  inline constexpr property(const char* name, data_type (class_type::*getter)() const, void (class_type::*setter)(data_type)) {
  }
};

property.h(使用concepts)

#include <type_traits>

template <typename class_type, typename data_type>
class property {
public:
  inline constexpr property(const char* name, data_type class_type::*property) requires(!std::is_reference_v<data_type>){
  }

  inline constexpr property(const char* name, data_type (class_type::*getter)() const, void (class_type::*setter)(data_type)) {
  }
};

解决方案

核心问题在于:property类模板的class_type和data_type是构造函数匹配前就必须确定的,导致第一个构造函数的data_type class_type::*签名会被强制求值——哪怕加了requires或enable_if,编译器推导模板参数时就会先解析这个签名,而指向引用的成员指针本身就是语法错误,直接触发报错。

要解决这个问题,需要把构造函数的参数推导和类模板参数解耦,以下是两种可行方案:

方案1:工厂函数+类特化

用外层工厂函数负责参数推导,再根据推导结果选择对应的property特化版本,避免类模板参数提前固定:

#include <type_traits>
#include <utility>

// 前置声明
template <typename ClassType, typename DataType>
class property;

// 工厂函数:处理成员变量(仅当DataType非引用时启用)
template <typename ClassType, typename DataType>
constexpr auto make_property(const char* name, DataType ClassType::*mem_ptr)
    requires (!std::is_reference_v<DataType>)
{
    return property<ClassType, DataType>(name, mem_ptr);
}

// 工厂函数:处理getter/setter对
template <typename ClassType, typename DataType>
constexpr auto make_property(const char* name, DataType (ClassType::*getter)() const, void (ClassType::*setter)(DataType))
{
    return property<ClassType, DataType>(name, getter, setter);
}

// 主模板:处理getter/setter对的情况
template <typename ClassType, typename DataType>
class property {
public:
    using getter_t = DataType (ClassType::*)() const;
    using setter_t = void (ClassType::*)(DataType);

    constexpr property(const char* name, getter_t getter, setter_t setter)
        : _name(name), _getter(getter), _setter(setter)
    {}

    constexpr DataType get(const ClassType& obj) const {
        return (obj.*_getter)();
    }

    constexpr void set(ClassType& obj, DataType value) const {
        (obj.*_setter)(std::move(value));
    }

private:
    const char* _name;
    getter_t _getter;
    setter_t _setter;
};

// 特化模板:处理成员变量,自动生成getter/setter
template <typename ClassType, typename DataType>
class property<ClassType, DataType> requires (!std::is_reference_v<DataType>) {
public:
    using mem_ptr_t = DataType ClassType::*;

    constexpr property(const char* name, mem_ptr_t mem_ptr)
        : _name(name), _mem_ptr(mem_ptr)
    {}

    constexpr DataType get(const ClassType& obj) const {
        return obj.*_mem_ptr;
    }

    constexpr void set(ClassType& obj, DataType value) const {
        obj.*_mem_ptr = std::move(value);
    }

private:
    const char* _name;
    mem_ptr_t _mem_ptr;
};

使用时把property替换为make_property即可:

constexpr static auto properties = std::make_tuple(
    make_property("name", &object::name, &object::set_name),
    make_property("index", &object::index, &object::set_index),
    make_property("flag", &object::_flag)
);

方案2:模板构造函数延迟推导

把property的构造函数改为模板,让参数推导延迟到构造函数内部,避免类模板参数提前固定导致的签名求值:

#include <type_traits>
#include <utility>

template <typename ClassType, typename DataType>
class property {
public:
    // 模板构造函数:仅处理成员变量(DataType非引用时启用)
    template <typename MemType>
    constexpr property(const char* name, MemType ClassType::*mem_ptr)
        requires std::is_same_v<std::remove_cv_t<MemType>, std::remove_cv_t<DataType>> && !std::is_reference_v<DataType>
    {
        // 存储成员指针,生成lambda形式的getter/setter
        _getter = [mem_ptr](const ClassType& obj) -> DataType {
            return obj.*mem_ptr;
        };
        _setter = [mem_ptr](ClassType& obj, DataType value) {
            obj.*mem_ptr = std::move(value);
        };
    }

    // 构造函数:处理getter/setter对
    constexpr property(const char* name, DataType (ClassType::*getter)() const, void (ClassType::*setter)(DataType))
    {
        _getter = [getter](const ClassType& obj) -> DataType {
            return (obj.*getter)();
        };
        _setter = [setter](ClassType& obj, DataType value) {
            (obj.*setter)(std::move(value));
        };
    }

    // 统一的get/set接口
    constexpr DataType get(const ClassType& obj) const {
        return _getter(obj);
    }

    constexpr void set(ClassType& obj, DataType value) const {
        _setter(obj, std::move(value));
    }

private:
    // C++23可直接用std::function,C++20需用自定义可调用类型或变体存储
    auto _getter;
    auto _setter;
};

这种方式下,传入成员函数指针时,编译器会直接匹配第二个构造函数,不会尝试解析第一个构造函数的成员指针签名,从根源避免了引用成员指针的语法错误。


内容的提问来源于stack exchange,提问作者fauxanadu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 00:28:09