Parsing strings at compile-time - if possible - c++11

I am trying to make compile time checks over the format of the printf. Here is the code.
#include <type_traits>
#include <iostream>
template <typename CHAR, typename ...ARGS>
constexpr size_t trace_cond(CHAR fmt, ARGS&&... args) {
//always needs to pass
return 1;
}
template <size_t N, typename ...ARGS>
constexpr size_t trace_cond(const char (&fmt)[N], ARGS&&... args) {
//return parse(fmt) == args;
return sizeof...(args) != 0;
}
#define TRACE(fmt, ...) { \
static_assert(trace_cond(fmt, __VA_ARGS__), "Wrong ARGS"); \
printf(fmt, ##__VA_ARGS__); \
}
int main(int argc, char* argv[]) {
//working fine
TRACE("%d %d\n", 2, 3);
const char* format = "%d %d\n";
//error
TRACE(format, 2, 3);
}
So when the format is known at compile time I want to have a check(using static_assert) and if it is not known then the check should be not called or always to pass
Obs:
Currently the code is not compiling because const char *format is not declared constexpr
The main should not be change because TRACE macro is used in a large codebase but changes to TRACE are more than welcome
So my question is:
Is there a way to skip static_assert or make it pass when fmt type is const char *

I think you can do what you want by moving the static_assert to the trace_cond
#include <type_traits>
#include <iostream>
template <size_t C, typename CHAR>
size_t trace_cond(CHAR fmt) {
//always needs to pass
return 1;
}
template <typename ...ARGS>
constexpr size_t count_args(ARGS&&... args) {
return sizeof...(args);
}
template <size_t C, size_t N>
constexpr size_t trace_cond(const char (&fmt)[N]) {
// TODO, parse fmt and do the needed checks
static_assert(C == 3, "");
return 1;
}
#define TRACE(fmt, ...) { \
trace_cond<count_args(__VA_ARGS__)>(fmt); \
printf(fmt, ##__VA_ARGS__); \
}
int main(int argc, char* argv[]) {
// Static check - hard coded to check for 3 arguments
TRACE("%d %d %d\n", 2, 3, 4);
const char* format = "%d %d\n";
// No checks performed
TRACE(format, 2, 3);
}

Related

Boost Serialization: Serialize/Export derived class without default constructor

I need to serialize all the options on my CommunicationLayer, which is basically a wrapper around serial port, which I will use in an initialization file of sorts.
This class doesn't have a default constructor, but implements a pure virtual class. When I first ran this I got the exception that I didn't register/export the derived class. So I did that. But when I added the export I got the exception that it couldn't find a save/load function:
no matching function for call to ‘save(boost_1_69_0::archive::xml_oarchive&, const amp::communication::RS485CommunicationLayer&, const boost_1_69_0::serialization::version_type&)’
save(ar, t, v);
This is true! But I can't use the save/load and use save_construct_data and load_construct_data, because the absence of a default constructor. How do I tell boost/serde to use these instead?? Is it the SPLIT_FREE macro?
I'm trying to work myself through the documentation, but it's really hard to find everything.
Following are the base and derived classes, I tried to shorten the derived class as much as possible.
//BASE
class ICommunicationLayer {
friend class boost::serialization::access;
template<class Archive>
inline void serialize(Archive & ar, const unsigned int file_version) {};
public:
virtual ~ICommunicationLayer();
virtual std::size_t write(const char* const buffer, std::size_t buffSize) = 0;
virtual std::size_t readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) = 0;
};
// DERIVED
#include "ICommunicationLayer.h"
#include <boost/asio.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/nvp.hpp>
namespace amp {
namespace communication {
class RS485CommunicationLayer final : public ICommunicationLayer {
public:
RS485CommunicationLayer(
const std::string& path,
unsigned int baud_rate,
// other options
);
~RS485CommunicationLayer();
std::size_t write(const char* const buffer, const size_t size) override;
std::size_t readUntil(std::vector<char>& buffer, char delim,std::chrono::microseconds timeout) override;
std::string getPath() const;
private:
friend class boost::serialization::access;
std::string path;
// rest of impl
};
} /* namespace communication */
} /* namespace amp */
BOOST_SERIALIZATION_SPLIT_FREE(amp::communication::RS485CommunicationLayer)
BOOST_CLASS_EXPORT(amp::communication::RS485CommunicationLayer)
namespace boost_1_69_0 {
namespace serialization {
template<class Archive>
inline void save_construct_data(Archive& ar, const amp::communication::RS485CommunicationLayer* comLayer, const unsigned int version) {
ar << boost::serialization::make_nvp(
BOOST_PP_STRINGIZE(amp::communication::ICommunicationLayer),
boost::serialization::base_object<amp::communication::ICommunicationLayer>(*comLayer)
);
ar << boost::serialization::make_nvp("path", const_cast<std::string>(comLayer->getPath());
// other options
}
template<class Archive>
inline void load_construct_data(Archive& ar, amp::communication::RS485CommunicationLayer* comLayer, const unsigned int version) {
ar >> boost::serialization::base_object<amp::communication::ICommunicationLayer>(*comLayer);
std::string path;
ar >> path;
// other options
new(comLayer) amp::communication::RS485CommunicationLayer(path, baudrate, /* other options */);
}
}
}
The construct data is an addition to regular serialization. You still need to serialize. As a matter of fact, serializing base_object needs to be in the constructed-object serialize implementation.
Also keep in mind that you need to EXPORT after including the relevant archive type(s).
You have a number of spots with redundant top-level const (even a const-cast). Top-level const on return type/arguments isn't part of the function signature.
Finally, I don't know what namespace boost_1_69_0 is, but that should not work. Use namespace boost. If you're playing with macros to define it like that, I'd suggest to stop doing that. Instead consider putting the overloads in the ADL-associated namespace for your types!
Here's the whole thing made self-contained and working:
Live On Coliru
#include <boost/serialization/access.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <chrono>
// BASE
namespace amp::communication {
using duration = std::chrono::steady_clock::duration;
class ICommunicationLayer {
friend class boost::serialization::access;
template <class Archive> inline void serialize(Archive&, unsigned){}
public:
virtual ~ICommunicationLayer();
virtual size_t write(char const* const buffer, size_t buffSize) = 0;
virtual size_t readUntil(std::vector<char>& buffer, char delim, duration timeout) = 0;
};
} // namespace amp::communication
// DERIVED
//#include "ICommunicationLayer.h"
#include <boost/asio.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/split_free.hpp>
namespace amp { namespace communication {
class RS485CommunicationLayer final : public ICommunicationLayer {
public:
RS485CommunicationLayer(std::string const& path, unsigned baud_rate)
: path(path)
, baud_rate(baud_rate) {}
~RS485CommunicationLayer();
size_t write(char const* const buffer, const size_t size) override;
size_t readUntil(std::vector<char>& buffer, char delim, duration timeout) override;
std::string getPath() const { return path; }
unsigned getBaudrate() const { return baud_rate; }
private:
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar& boost::make_nvp("ICommunicationLayer",
boost::serialization::base_object<ICommunicationLayer>(*this));
}
std::string path;
unsigned baud_rate;
// rest of impl
};
// ADL resolved overloads
template <class Archive>
inline void save_construct_data(Archive& ar, RS485CommunicationLayer const* p, unsigned) {
auto path = p->getPath();
unsigned baud_rate = p->getBaudrate();
ar& BOOST_NVP(path) & BOOST_NVP(baud_rate);
}
template <class Archive>
inline void load_construct_data(Archive& ar, RS485CommunicationLayer* p, unsigned) {
std::string path;
unsigned baud_rate;
ar >> BOOST_NVP(path) >> BOOST_NVP(baud_rate);
new (p) RS485CommunicationLayer(path, baud_rate);
}
}} // namespace amp::communication
BOOST_SERIALIZATION_ASSUME_ABSTRACT(amp::communication::ICommunicationLayer)
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <iostream>
#include <sstream>
BOOST_CLASS_EXPORT(amp::communication::ICommunicationLayer)
BOOST_CLASS_EXPORT(amp::communication::RS485CommunicationLayer)
int main() {
using namespace amp::communication;
using Ptr = std::shared_ptr<ICommunicationLayer>;
std::vector<Ptr> ptrs{nullptr, std::make_shared<RS485CommunicationLayer>("hello", 115'200)}, roundtrip;
ptrs.push_back(ptrs.back()); // one duplicate for testing purposes
std::stringstream xml;
{
boost::archive::xml_oarchive oa(xml);
oa << boost::make_nvp("root", ptrs);
}
std::cout << xml.str();
{
boost::archive::xml_iarchive ia(xml);
ia >> boost::make_nvp("root", roundtrip);
}
bool verify = roundtrip.size() == ptrs.size();
verify &= (roundtrip.at(0) == nullptr);
verify &= (roundtrip.at(1) == roundtrip.at(2));
std::cout << "Roundtrip verify " << (verify?"OK":"FAILED") << std::endl;
}
// to satisfy linker
namespace amp { namespace communication {
ICommunicationLayer::~ICommunicationLayer() = default;
RS485CommunicationLayer::~RS485CommunicationLayer() = default;
size_t RS485CommunicationLayer::write(char const*, size_t) { return 0; }
size_t RS485CommunicationLayer::readUntil(std::vector<char>&, char, duration) { return 0; }
}} // namespace amp::communication
Prints
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="19">
<root class_id="0" tracking_level="0" version="0">
<count>3</count>
<item_version>1</item_version>
<item class_id="1" tracking_level="0" version="1">
<px class_id="-1"></px>
</item>
<item>
<px class_id="2" class_name="amp::communication::RS485CommunicationLayer" tracking_level="1" version="0" object_id="_0">
<path>hello</path>
<baud_rate>115200</baud_rate>
<ICommunicationLayer class_id="3" tracking_level="0" version="0"></ICommunicationLayer>
</px>
</item>
<item>
<px class_id_reference="2" object_id_reference="_0"></px>
</item>
</root>
</boost_serialization>
Roundtrip verify OK
Afterthought
Perhaps the best location for load/save construct data is as hidden friends. That way you don't even need public accessors for every bit of construct-data:
Live On Coliru
class RS485CommunicationLayer final : public ICommunicationLayer {
public:
RS485CommunicationLayer(std::string const& path, unsigned baud_rate)
: path(path)
, baud_rate(baud_rate) {}
~RS485CommunicationLayer() override;
size_t write(char const* const buffer, const size_t size) override;
size_t readUntil(std::vector<char>& buffer, char delim, duration timeout) override;
private:
std::string path;
unsigned baud_rate;
// rest of impl
template <class Archive>
friend void save_construct_data(Archive& ar, RS485CommunicationLayer const* p, unsigned) {
auto path = p->path;
unsigned baud_rate = p->baud_rate;
ar& BOOST_NVP(path) & BOOST_NVP(baud_rate);
}
template <class Archive>
friend void load_construct_data(Archive& ar, RS485CommunicationLayer* p, unsigned) {
std::string path;
unsigned baud_rate;
ar >> BOOST_NVP(path) >> BOOST_NVP(baud_rate);
new (p) RS485CommunicationLayer(path, baud_rate);
}
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar& boost::make_nvp("ICommunicationLayer",
boost::serialization::base_object<ICommunicationLayer>(*this));
}
};

Convert "Tcl_Obj* const objv[]" to "char** argv"

I'm using Tcl 8.6 and I'm trying to do something like this to add functions to the tcl interpreter
Tcl_Interp* interp,
void init() {
interp = Tcl_CreateInterp();
}
void add_tcl_function(char* cmd, function<int(int,char**)> F) {
obj2argv* o2a = new obj2argv;
auto lambda_proc = [&](
ClientData cdata,
Tcl_Interp* interp,
int objc,
Tcl_Obj* const objv[])
{
o2a->set(objc, objv);
F(objc, o2a->get_argv());
};
auto lamba_delete = [&](
delete o2a;
};
Tcl_CreateObjCommand(interp, cmd, lamda_proc, NULL, lamda_delete);
}
What I'm wondering is how to convert "Tcl_Obj* const objv[]" to "char** argv"?
I was thinking about creating a class:
class obj2argv {
obj2argv();
void set(int objc, Tcl_Obj* const objv[]);
char** get_argv();
private:
//...
};
any ideas on how to implement set() and get_argv()?
Is there an easier way to do this?
Thanks.
obj2argv* o2a = new obj2argv;
If you're interfacing a function that's fundamentally working with const char** for arguments, you should register the function with Tcl_CreateCommand and let Tcl handle the mapping to strings for you. It already has all the mechanisms required.
More formally, you are dealing with a gluing function with this signature:
typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
The CONST84 should be read as being plain const in all new code, and ClientData is a pointer-sized value that Tcl just hands around and never inspects (same as with your existing code).
If you are going to do the mapping yourself, Tcl_GetString takes a Tcl_Obj* and returns the char* representation of it. The representation should be usually treated as const; it simply isn't formally typed as such for historical reasons.
I wanted to add some more information:
I gave up on using lambda's because when I added capture list it won't convert the lambda to a function pointer for some reason. So I went with the traditional approach (see below). EXCEPT: I still have not idea why the TCL document says
typedef int Tcl_CmdProc(
ClientData clientData,
Tcl_Interp *interp,
int argc,
const char *argv[]);
But the compiler requires this to compile:
typedef int Tcl_CmdProc(
ClientData clientData,
Tcl_Interp *interp,
int argc,
Tcl_Obj* const* argv);
The Code:
int cmd_dispatch(
ClientData clientData,
Tcl_Interp* interp,
int argc,
Tcl_Obj* const* argv)
{
function<int(int,char**)> F = *(function<int(int,char**)>*)clientData;
return F(argc, (char**) argv); // <= CAST DOESN'T SEEM RIGHT
}
void cmd_delete(ClientData clientData)
{
}
void add_tcl_function(const char* cmd, function<int(int,char**)> F) {
Tcl_CreateObjCommand(interp, cmd, cmd_dispatch, (void*)&F, cmd_delete);
}
VERSION 2:
struct cmd_data {
//Tcl_Interp* interp,
function<int(int,char**)> F;
int argc;
char* argv[MAX_ARGS];
};
int cmd_dispatch(
ClientData clientData,
Tcl_Interp* interp,
int argc,
Tcl_Obj* const* objv)
{
auto cmd_data1 = (struct cmd_data*) clientData;
cmd_data1->argc = argc;
for(int i=0; ((i < argc) && (i < MAX_ARGS)); i++) {
cmd_data1->argv[i] = Tcl_GetString(objv[i]);
// Who owns object returned by Tcl_GetString?
// memory leak? or invalid after return from function?
// garbage collected by tcl interp?
}
return cmd_data1->F(argc, cmd_data1->argv);
}
void cmd_delete(ClientData clientData)
{
auto cmd_data1 = (struct cmd_data*) clientData;
if (cmd_data1) {
delete cmd_data1;
}
}
void add_tcl_function(const char* cmd, function<int(int,char**)> F) {
auto cmd_data1 = new struct cmd_data;
cmd_data1->F = F;
Tcl_CreateObjCommand(interp, cmd, cmd_dispatch, (void*)cmd_data1, cmd_delete);
}
void init_tcl_commands() {
auto lambda_hello = [&](int argc ,char** argv) -> int {
cout << "HELLO WORLD!\n";
return 0;
};
tcl_backend::add_tcl_function("hello", lambda_hello);
}

How to make msvc 14 evaluate my constexpr at compile time

I am experimenting with c++ constexpr. I am implementing a HashedString class using FNV-1a hash.
Everything seems fine except that visual studio 2015 update 3 doesn't seem to evaluate the constexpr at compile time.
I added a static_assert and it shows no error, but in my test's disassembly it is clear that there is an explicit call to the constexpr function instead of the precomputed value.
I also tried with g++ and clang and they both are able to evaluate the constexpr at compile time.
Here is my test code:
#include <cstdint>
#include <cstddef>
#include <string>
class HashedString {
public:
//value working only for a 32bit hash
constexpr static size_t defaultOffset = 2166136261u;
constexpr static size_t prime = 16777619u;
/**
* Compute the hash of a string at compile time using FNV-1a hash
* https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80 %93Vo_hash_function
*/
template<std::size_t N>
constexpr HashedString(const char(&a)[N]) noexcept
: mHash(hash(a))
#if defined(_DEBUG)
, mString(a)
#endif
{
}
explicit constexpr HashedString(size_t h) noexcept : mHash(h) {}
constexpr static size_t hash(const char *const aString, const uint32_t val = defaultOffset) noexcept
{
return (aString[0] == '\0') ? val : hash(&aString[1], (val ^ uint32_t(aString[0])) * prime);
}
constexpr bool operator==(const HashedString & hs) const { return mHash == hs.mHash; }
constexpr bool operator==(const size_t & h) const { return mHash == h; }
constexpr bool operator!=(const HashedString & hs) const { return mHash != hs.mHash; }
constexpr bool operator!=(const size_t & h) const { return mHash != h; }
constexpr bool operator<(const HashedString & hs) const { return mHash < hs.mHash; }
private:
const size_t mHash = 0;
#if defined(_DEBUG)
const char* mString = nullptr;
#endif
};
static_assert(HashedString("FNV Hash Test") == 0xF38B3DB9, "HashedString of 'FNV Hash Test' shoulb be equal to 0xF38B3DB9");
int main(int , char**) {
constexpr HashedString hs("FNV Hash Test");
return hs == 0xF38B3DB9;
}
So my question is: Is there a way to make visual studio compute my constexpr at compile time?
Changing main to:
constexpr auto hash = HashedString::hash("FNV Hash Test");
return hash == 0xF38B3DB9;
or
constexpr HashedString hs("FNV Hash Test");
constexpr auto answer = hs == 0xF38B3DB9;
return answer;
will cause the hash to be computed at compile time. The way your code was there was no demand on the compiler to compute the hash at compile-time. By requiring the compiler to initialize a constexpr variable, it is forced to compute the value at compile-time. Which reduced main's code to:
mov eax,1
ret
Booyah! for VS2015's SSA optimizations.

GCC produce "could not convert" error when using aggregate initialization

I'm trying to make C-string size calculation at compile time, using code like this:
#include <stdio.h>
#include <stdint.h>
class StringRef
{
public:
template<int N>
constexpr StringRef(const char (&str)[N])
: m_ptr(str), m_size(uint32_t(N-1)) {}
constexpr const char *constData() const
{ return m_ptr; }
private:
const char *m_ptr;
uint32_t m_size;
};
struct S
{
StringRef str;
};
constexpr static const struct S list[] =
{
"str",
};
int main()
{
printf("%s\n", list[0].str.constData());
return 0;
}
In clang-3.7 everything is fine, but in GCC 4.9.3-5.3 I get:
error: could not convert '(const char*)"str"' from 'const char*' to
'StringRef'
It can be fixed by adding explicit braces:
constexpr static const struct S list[] =
{{
{ "str" },
}};
But code became ugly and, still, clang somehow understand it correctly.
How can I make gcc understand array initialization without explicit braces?

variadic template argument for std::function

Recently, I've been working on a little project alongside my c++ game-dev engine : it's a programming language, written in C++, in one header, named kickC. Here is what I have done so far : (See question below)
#ifndef KICK_C_INCLUDED_H
#define KICK_C_INCLUDED_H
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <exception>
#include <functional>
#include <unordered_map>
#include <vector>
#define LOG(x) std::cout << x << std::endl;
namespace strutil
{
inline unsigned CountWords(const std::string& value){
std::string temp = value;
std::replace_if(temp.begin(), temp.end(), std::ptr_fun<int, int>(std::isspace), ' ');
temp.erase(0, temp.find_first_not_of(" "));
if(temp.empty())
return 0;
return std::count(temp.begin(), std::unique(temp.begin(), temp.end()), ' ') + !std::isspace(*value.rbegin());
}
}
class KickCException : std::exception
{
public:
explicit KickCException(const char* msg, bool fatal = false)
: msg_(msg){}
explicit KickCException(const std::string& msg)
: msg_(msg){}
virtual ~KickCException() throw(){}
virtual const char* what() const throw(){
return std::string("[error :] [")
.append(msg_)
.append("]")
.c_str();
}
protected:
std::string msg_;
};
class KickCFileException : KickCException
{
public:
explicit KickCFileException(const char* msg)
: KickCException(msg){}
explicit KickCFileException(const std::string& msg)
: KickCException(msg){}
virtual ~KickCFileException() throw(){}
const char* what() const throw() override{
return std::string("[file error :] [")
.append(msg_)
.append("]")
.c_str();
}
};
class KickCEmptyStringException : KickCException
{
public:
explicit KickCEmptyStringException(const char* msg)
: KickCException(msg){}
explicit KickCEmptyStringException(const std::string& msg)
: KickCException(msg){}
virtual ~KickCEmptyStringException() throw(){}
const char* what() const throw() override{
return std::string("[empty string error :] [")
.append(msg_)
.append("]")
.c_str();
}
};
class KickCAPIBehaviourImplementation
{
public:
KickCAPIBehaviourImplementation(){}
~KickCAPIBehaviourImplementation(){}
void AddDefined(const std::string& str, std::function<void(void)> func){
m_values[str] = func;
}
void ParseAndApplyLine(const std::string& line){
std::istringstream iss(line);
for(unsigned i = 0; i < strutil::CountWords(line); ++i){
static std::string word = "";
iss >> word;
for(auto it_map = m_values.begin(); it_map != m_values.end(); ++it_map){
if(it_map->first == word)
{
(it_map->second)(/*HERE ! GIVE SOME ARGUMENTS ! */);
}
}
}
}
private:
std::unordered_map<std::string, std::function<void(void)>> ///so far, args is void... m_values;
};
#endif //KICK_C_INCLUDED_H
///src
int main(int argc, const char** args){
std::ifstream file("script.kick");
KickCAPIBehaviourImplementation kickCApiBehaviour;
try{
if(!file.is_open())
throw KickCFileException("unvalid fileName taken at input");
kickCApiBehaviour.AddDefined("print", [&](void){std::cout << "print found !" << std::endl;});
while(!file.eof()){
std::string line;
std::getline(file, line);
kickCApiBehaviour.ParseAndApplyLine(line);
}
}catch(KickCException& e){
LOG(e.what());
}
file.close();
std::cin.get();
}
So here is the Question : I would like to pass std::function (see class KickCAPIBehaviourImplementation ) a variable argument of types : I need to use variatic templates, of course, but the question how can I implement it so i end up calling my functions like this ?
kickCApiBehaviour.AddDefined("print", [&](int arg1, char * arg2, int arg3){std::cout << arg1 << arg2 << arg3 << std::endl;});
Move the parser into the std::function.
Where you add the function, include a signature:
// helper type:
template<class T>struct tag{using type=T;};
kickCApiBehaviour.AddDefined(
"print", // the name
tag<void(int,char*,int)>{}, // the signature
[&](int arg1, char * arg2, int arg3){
std::cout << arg1 << arg2 << arg3 << std::endl;
} // the operation
);
store a std::function< error_code(ParserState*) >. Inside AddDefined, store a lambda that includes a call to the code that parses arguments and calls the passed in lambda:
template<class R, class...Args, class F>
void AddDefined(std::string name, tag<R(Args...)>, F f) {
std::function< error_code(ParserState*) > r =
[f](ParserState* self)->error_code {
// here, parse each of Args... out of `self`
// then call `f`. Store its return value,
// back in `self`. If there is a parse error (argument mismatch, etc),
// return an error code, otherwise return no_error
};
m_values[name] = r;
};
then m_values contains the operation "take a parser state, and parse the arguments, and call the function in question on them".

Resources