Reputation: 487
I'm writing a helper function for logging purposes: it collects call-site
information while also creating a Error
-typed object.
template<typename ErrorTy, typename... ArgTys>
std::unique_ptr<Error> makeError(const char* fileName = __builtin_FILE(),
int lineNum = __builtin_LINE(),
const char* funcName = __builtin_FUNCTION(), ArgTys &&... args) {
return std::make_unique<ContextualError<ErrorTy>>(fileName, lineNum, funcName,
std::forward<ArgTys>(args)...);
}
// ...
template<typename ErrorTy>
struct ContextualError : ErrorTy {
template<typename... ArgTys>
ContextualError(std::string_view fileName, int lineNum,
std::string_view funcName, ArgTys &&... args) :
fileName_(fileName), lineNum_(lineNum), funcName_(funcName),
ErrorTy(std::forward<ArgTys>(args)...) {}
// ...
};
As the actual Error
classes can have arbitrary constructors, I was hoping to
get makeError
and the ContextualError
constructor to perfectly forward
everything. Unfortunately, calling makeError
like this will try to fill the
first three optional arguments, rather than skipping them and filling the
parameter pack:
auto err = makeError<FileError>("foo.exe", "Invalid header");
// error: no matching function for call to 'makeError'
// note: candidate function template not viable: no known conversion
// from 'const char [15]' to 'int' for 2nd argument
// It's trying to replace the default values:
auto err = makeError<FileError>(fileName: "foo.exe", lineNum: "Invalid header", funcName: __builtin_FUNCTION(), args...: );
// But I only want to supply the parameter pack args:
auto err = makeError<FileError>(fileName: __builtin_FILE(), lineNum: __builtin_LINE(), funcName: __builtin_FUNCTION(), args...: "foo.exe", "Invalid header");
Moving the parameter pack to the front of the arguments list doesn't seem to fix this either. Is there any way to achieve this perfect forwarding while keeping the optional arguments?
Minimum reproducible example on Godbolt
Upvotes: 1
Views: 342
Reputation: 275500
template<class ErrorTy>
auto makeError(
const char* fileName = __builtin_FILE(),
int lineNum = __builtin_LINE(),
const char* funcName = __builtin_FUNCTION()
) {
return [=](auto&&... args) {
return std::make_unique<ContextualError<ErrorTy>>(
fileName, lineNum, funcName,
decltype(args)(args)... // yes, this perfect fowards
);
};
}
use is:
auto err = makeError<FileError>()("foo.exe", "Invalid header");
see the extra ()
. The optional arguments go in ()
, and the forwarded arguments go into the second set of ()
.
you could also make makeError
a class
and give it an operator()
that makes the actual error, and give it default arguments for its ctor. Then you'd get:
auto err = makeError<FileError>{}("foo.exe", "Invalid header");
which might be less confusing, or more confusing. One of those.
Upvotes: 5