Alvaro Palma Aste
Alvaro Palma Aste

Reputation: 409

Mixing const and non-const variables on variadic template

I've developed a couple of generic reader and writer wrapper functions (mostly to avoid having to handle exceptions on each part of the code that reads or writes a file). My functions have these signatures:

For writing:

/** Generic writer function alias for free functions */
template<typename ... Args>
using WriterFunction = void (*)(std::ofstream &, Args const & ... args);
/**
 * \brief Wrapper used to encapsulate a generic free function writing to an output stream
 * \param[in] outFileName Name of the output file to be written.
 * \param[in] writeFunction Function used to export the data.
 * \param[in] ... Variable number of arguments to be passed to the previous function.
 */
template<typename ... Args>
void writerFunctionWrapper(
    std::string const &outFileName,
    WriterFunction<Args ...> writeFunction,
    Args const & ... args);

and for reading:

/** Generic reader function alias for free functions */
template<typename ... Args>
using ReaderFunction = void (*)(std::ifstream &, Args & ... args);
/**
 * \brief Wrapper used to encapsulate a generic free function reading from an input stream
 * \param[in] inFileName Name of the input file to be read.
 * \param[in] readFunction Function used to import the data.
 * \param[in] ... Variable number of arguments to be passed to the previous function.
 */
template<typename ... Args>
void readerFunctionWrapper(
    std::string const &inFileName,
    ReaderFunction<Args ...> readFunction,
    Args & ... args);

So, the main difference is that the writer considers the input parameters as const, as I expect to read from them into the std::ostream, while viceversa, the reader considers the input parameters as non-const, as I expect to write to them from the std::istream.

This works fine, but I've come to a point where I'd like to have reader functions with const parameters in their variable list, or writer functions with non-const parameters as well. However, I can't figure out how to do that when the list of parameters is variable.

I tried modifying my alias and templates to

template<typename ... ArgsConst, typename ... ArgsNonConst>
using WriterFunction = void (*)(std::ofstream &, ArgsConst const & ... argsConst, ArgsNonConst & ... argsNonConst);
template<
    typename ... ArgsConst,
    typename ... ArgsNonConst>
void writerFunctionWrapper(
    std::string const &outFileName,
    WriterFunction<ArgsConst ..., ArgsNonConst ...> writeFunction,
    ArgsConst const & ... argsConst,
    ArgsNonConst & ... argsNonConst);

which has the downside to force an order in the way arguments are placed in the writeFunction(), first the const, then the non-const, but this is a lesser evil. However, that doesn't work for me:

/home/code/Utils/include/file_utils.h:42:11: error: parameter pack ‘ArgsConst’ must be at the end of the template parameter list
  template<typename ... ArgsConst, typename ... ArgsNonConst>

I also tried simply mixing const and non-const parameters by extending my alias and template as:

/** Generic writer function alias for free functions */
template<typename ... Args>
using WriterFunctionGeneral = void (*)(std::ofstream &, Args const & ... argsConst, Args & ... argsNonConst);
template<typename ... Args>
void writerFunctionWrapper(
    std::string const &outFileName,
    WriterFunctionGeneral<Args ...> writeFunctionGeneral,
    Args const & ... argsConst,
    Args & ... argsNonConst);

But that didn't work either when I tried to use it on a function mixing const and non-const parameters, as:

void writeSomeEntity(
    std::ofstream &outWriter,
    ConstOutVar const &constVar,
    NonConstOutVar &nonConstVar);

And invoke it as:

ConstOutVar constVar;
NonConstOutVar nonConstVar;
...
Utils::writerFunctionWrapper(outFileName, &writeSomeEntity, constVar, nonConstVar);

but that didn't work either:

/home/code/OutputWriter/src/entity_writer.cpp:46:122: error: no matching function for call to ‘writerFunctionWrapper(std::string&, void (*)(std::ofstream&, const ConstOutVar &, NonConstOutVar &), ConstOutVar &, NonConstOutVar &)’
  Utils::writerFunctionWrapper(outFileName, writeSomeEntity, constOutVar, nonConstOutVar);

(Notice how the error shows constOutVar & instead of constOutVar const &)

I noticed [this question] (Variadic template trouble matching const and non-const std::string), but that's not really the situation I'm describing here.

Any suggestions?

Thanks a lot in advanced.

Upvotes: 1

Views: 628

Answers (1)

max66
max66

Reputation: 66210

Any suggestions?

Just three.

(1) Accept the function in your wrapper as a generic template type, ignoring the exact type of the arguments received; this way your wrapper is extremely more flexible because can accept also a std::function or a lambda (also a generic lambda) or (almost the same) a struct/class with an operator() (maybe a template one). This is impossible is you accept only function pointers

(2) receive the arguments as universal references forwarding references and use perfect forwarding; perfect forwarding is designed exactly to solve to problem you're facing.

Following suggestions (1) and (2), you needs a single wrapper (why do you needs a reader and a writer wrapper if both of they accept input only and input/output arguments?) and the following could be a simplified example

template <typename F, typename ... Args>
void FunctionWrapper(F && f, Args && ... args)
 { std::forward<F>(f)(std::forward<Args>(args)...); }

But if you really really (really!) want one or more wrappers accepting only traditional function pointers, here come suggestion (3)

(3) use two variadic sets of template types for arguments; one set for the function pointer and one set for arguments; I mean... something as follows

template <typename ... FAs, typename ... As>
void FunctionWrapperPnt (void(*f)(FAs...), As && ... as)
 { f(std::forward<As>(as)...); }

This is important to avoid that you can pass a function that receive (by example) a std::string and an argument that is a char const [] (a string literal) giving an error in deducing types.

Upvotes: 3

Related Questions