Search code examples
c++c++20template-meta-programmingsfinaec++23

Discluding Constructor Signature from Evaluation in C++23


C++ Standard: 20 or 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 w/ 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 w/ 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)) {
  }
};

I am trying to keep consideration of the signature of the first constructor from being evaluated if data_type is any reference type since a pointer to a reference member is illegal (in the example above, name/set_name trigger this condition). I would have expected, being constexpr, that the first constructor wouldn't even be evaluated for the name/set_name case yet it is. So I tried using enable_if and a requires statement but neither prevents the signature itself from being evaluated (which in hindsight makes sense since it has to evaluate the signature to determine class_type and data_type).

Is there any way to achieve what I am attempting to achieve here (namely being able to accept a class member and create a lambda for the getter/setter or accept a getter/setter pair in a constexpr context within the same object)?


Solution

  • data_type is in the parameter list, so it will always be instantiated.

    You can template the data_type template parameter for the constructor and provide deduction guides yourself

    template <typename class_type, typename data_type>
    class property {
    public:
      template<class dummy = data_type>
      constexpr property(const char* name, dummy class_type::*property) {
      }
      
      template<class dummy = data_type>
      constexpr property(const char* name, dummy (class_type::*getter)() const, void (class_type::*setter)(dummy)) {
      }
    };
    
    template <typename class_type, typename data_type>
    property(const char*, data_type class_type::*) -> property<class_type, data_type>;
    template <typename class_type, typename data_type>
    property(const char*, data_type (class_type::*)() const, void (class_type::*)(data_type)) 
      -> property<class_type, data_type>;