Brandon
Brandon

Reputation: 487

Filling a parameter pack by skipping optional arguments

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

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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 ().

Live example based off yours.

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

Related Questions