简体   繁体   中英

Can I get rid of this class member in this template?

I had a templated class that had a getter function and a setter function pointer. When I created an object of this class and I didn't want its setter to do anything I'd just pass nullptr as the setter function argument. Then at runtime, it would check if its setter pointer was nullptr to see whether it should call the setter.

Using a function pointer isn't good enough now because I want the class to be able to store a lambda. So instead of a function pointer the setter is now a template type argument. Before I used to pass a nullptr to signify that there was no setter, but now I can't, so I use a dummy char member, as you can see:

template <typename GetFunctionType, typename SetFunctionType = char>
class MyClass
{
public:
   MyClass(GetFunctionType getFunction, SetFunctionType setFunction = char(0)) 
      : getFunction(getFunction), setFunction(setFunction)
   {}
   GetFunctionType getFunction;
   SetFunctionType setFunction;
   typedef decltype(getFunction()) ReturnType;

   void set(ReturnType value)
   {
      if constexpr (std::is_invocable_v<decltype(setFunction), ReturnType>)
      {
         setFunction(value);
         std::cout << "Setter called\n";
      }
      else
      {
         // ELSE DO NOTHING
         std::cout << "Object has no setter\n";
      }
   }
};

int main()
{
   MyClass foo([]() { return 7; }, [](int val) { std::cout << "You have set the value\n"; });

   MyClass foo2([]() {return 7; }); // THIS OBJECT HAS NO SETTER, BUT HAS A CHAR 
                                    // MEMBER THAT I USED AS A DUMMY
   foo.set(1);
   foo2.set(1);
}

My question is do I need that dummy char in cases where the object has no setter function?

Using a function pointer isn't good enough now because I want the class to be able to store a lambda!

Not completely true!

You could store the capture-less lambdas to the typed-function pointers.

See [expr.prim.lambda.closure] (sec 7)

The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator.

In addition to that, normally a getter would have a signature

ReturnType /*const-ref*/ <function>();

similarly, the setter would have

void <function>(ArgumentType) /*const*/; // where ReturnType  == ArgumentType usually

Combining these two information, I am suggesting the following re-structuring of your class.

( See live demo online )

#include <iostream>
#include <string>

template <typename ArgType>
class MyClass final
{
   // alias types
   using GetFunctionType = ArgType(*)(void);
   using SetFunctionType = void(*)(ArgType);

   GetFunctionType getFunction;
   SetFunctionType setFunction;
public:
   // Now you can set the function pointer to by default `nullptr`
   MyClass(GetFunctionType getFunction = nullptr, SetFunctionType setFunction = nullptr)
      : getFunction{getFunction}
      , setFunction{setFunction}
   {}

   void set(ArgType value) const noexcept
   {
      if (getFunction && setFunction)  // runtime nullptr check
      {
         setFunction(value);
         std::cout << "Setter called\n\n\n";
      } else {
         std::cout << "Object has no setter\n\n";
      }
   }
};

int main()
{
   // mention the argument type
   MyClass<int> foo(
      []() { return 7; },
      [](int val) { std::cout << "You have set the value: " << val << "\n"; }
   );

   MyClass<std::string> foo2([]() {return std::string{}; }); // also works

   foo.set(1);
   foo2.set("String");
}

It would be good if there was a way you could pass void or ' something ' so that when you declare a class member like this: T setFunction ; the compiler just removes it from the class.

To my understanding, when you do the partial specialization , you do not even need to declare the setFunction at all.

Following is the example code, in which the first specialization

template <typename Getter, typename Setter>
class MyClass final{};

handles the when you provide Getter and Setter cases, whereas the second one handles the no setter situation.

template <typename Getter>
class MyClass<Getter, std::nullptr_t> final

Unfortunately, you still need to specify the second argument (ie std::nullptr_t{} ) in order to choose the correct specialization.

( See live demo online )

#include <iostream>
#include <cstddef>  // std::nullptr_t

template <typename Getter, typename Setter>
class MyClass final
{
   Getter mGetFunction;
   Setter mSetFunction;
   using ReType = decltype(mGetFunction());
   static_assert(std::is_invocable_v<decltype(mGetFunction)>, " Getter is not callable!");
   static_assert(std::is_invocable_v<decltype(mSetFunction), ReType>, " Setter is not callable!");

public:
   MyClass(Getter mGetFunction, Setter mSetFunction) noexcept
      : mGetFunction{ mGetFunction }
      , mSetFunction{ mSetFunction }
   {}
   void set(ReType value) const noexcept {
      mSetFunction(value);
      std::cout << "Setter called\n";
   }

};

template <typename Getter>
class MyClass<Getter, std::nullptr_t> final
{
   Getter mGetFunction;
   using ReType = decltype(mGetFunction());
   static_assert(std::is_invocable_v<decltype(mGetFunction)>, " Getter is not callable!");

public:
   MyClass(Getter mGetFunction, std::nullptr_t) noexcept
      : mGetFunction{ mGetFunction }
   {}

   void set(ReType value) const noexcept {
      std::cout << "Object has no setter\n";
   }
};

int main()
{
   MyClass foo{
      []() { return 7; },
      [](int val) { std::cout << "You have set the value\n"; }
   };
   foo.set(1);

   //unfortunately, needed to pass second argument for class instantiation
   MyClass foo2([]() {return 7; }, std::nullptr_t{});
   foo2.set(1);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM