Search code examples
c++templatessequencesvariadic

c++ passing sequence upper range to variadic function template


I'm calling APIs in a specific way with the help of templates and I have left one problem with passing a constant parameter.

My try with int bound:

    template <typename F, typename ...Ts> 
          static int f3(int bound, CString file, int line, CString Caller,
                        CString f_name, F f, Ts&& ...ts) {
                 int err = fn(bound, file, line, Caller, f_name,
                 f, std::tuple<Ts...>(ts...), seq3<bound>{},  // error C2975 
                 seq1<sizeof...(Ts)>{});                  
                 return err;
    }

In main:

int const bound; 
bound = 4; 

err = fn(bound, api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path");

compiler error C2975: 'N': invalid template argument for 'seq3', expected compile-time constant expression

How to fix this?

My workaround by now:

err = f3(api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path");

f3 is a specialisation for an API with 3 arguments, because I'm up to now not able to pass in the upper bound - 4 in this case - for generating a sequence: <1,2,3>. This sequence is needed to call an API with 3 arguments, where the tupel starts at the parameter rval in f3().

Background:

api is a #define

f3 calls the API.

f3 handles the return value of the API at the 0 position of the sequence/tupel.

f3 calls with all parameters another variadic function for logging debug informations.

One tupel and two sequences for two function calls.

PROBLEM:

I want to pass a parameter to control the upper bound of a sequence not given by the tupel-size but by the API function signature.

I want only one fn() for all APIs and not f0(), f1(), f2(), f3() ..... for APIs with 0, 1, 2, 3 ... arguments.

I want something like this:

err = fn(seq3<4>, api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path")

Here's my working code:

#include <windows.h>
#include <atlstr.h>
#include <tuple>
#include <utility>

template <int ... Ns> struct seq_3 {};                                  
template <int ... Ns> struct seq3_n {};  

template <int I, int ... Ns> struct seq3_n<I, Ns...>{
   using type = typename seq3_n<I - 1, I - 1, Ns...>::type;};

template <int ... Ns> struct seq3_n<1, Ns...>{
// skip first argument : rval, because it doesn't fit to API,
// but needed for calling other function    
   using type = seq_3<Ns...>;                };

template <int N>
   using seq3 = typename seq3_n<N>::type;   

template <int ... Ms> struct seq_1 {};                          
template <int ... Ms> struct seq1_n {};    
template <int J, int ... Ms> struct seq1_n<J, Ms...>{
   using type = typename seq1_n<J - 1, J - 1, Ms...>::type; };    
template <int ... Ms> struct seq1_n<0, Ms...> {
   using type = seq_1<Ms...>;                };
template <int M>
   using seq1 = typename seq1_n<M>::type;       

template <typename F, typename TUP, int ... INDICES3, int ... INDICES1>                        
   static int fn(CString file,  int line, CString Caller, CString f_name,
              F f, TUP tup, seq_3<INDICES3...>, seq_1<INDICES1...>) {   
                int err = 0;
                // handling of rval = first element of tuple 
                std::get<0>(tup) = f(std::get<INDICES3>(tup) ...);  // calling API  
                err = GetLastError();   
                /* calling next function (variadic too) with same tupel, but other sequence 
                 myOpenDebugOutputString(project, file, line, Caller, f_name, std::get<INDICES1>(tup) ..., "stop");
                */ 
                return err; }

template <typename F, typename ...Ts> 
   static int f3(CString file, int line, CString Caller, CString f_name,
              F f, Ts&& ...ts)  {
                int err = fn(file, line, Caller, f_name,
                f, std::tuple<Ts...>(ts...), seq3<4>{},  // sequence fixed by f3 
                seq1<sizeof...(Ts)>{});                  // 3 arguments api  + skip 1 rval = 4 
                return err;                              // given by signature of API 
}


int main() {    
    // for calling simple API GetModulFileName with 3 arguments     
    //                                      returns len(path)   
    wchar_t     path[MAX_PATH];     
    DWORD           rval = 0;   
    int         err = 0;
    rval = GetModuleFileName( nullptr, path, MAX_PATH);     
    err  = GetLastError(); 

#define api(a)  __FILE__, __LINE__, __func__, L#a, a   
// L#a becomes L"GetModuleFileName" 

    err = f3(api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path");   

    return 0; }

Thanks in advance.

P.S. I'm using Microsoft Visual Studio 2015

Update:

I tried following in template api_call from Richard Hodges solution.

std::tuple<GivenArgs...> tup(args...);   

// OK, but only for an api with 3 arguments 
callsite.function(std::get<0>(tup), std::get<1>(tup), std::get<2>(tup));

// compiler error too many arguments  
callsite.function(std::forward<GivenArgs>(args)..., seq1<callsite.nofArgs()>{}); 

// compiler error too few arguments
callsite.function(tup, seq1<callsite.nofArgs()>{}); 

Remarks:

seq1<3> = seq_1<0,1,2>

callsite.nofArg() = 3

How to get the correct number of arguments?


Solution

  • It's not entirely clear how you want to handle errors etc. I have assumed returning a tuple of error code and value.

    Here is a general pattern which I think will do what you want. You'll need to be careful around specialisations and overloads of emit_log, particularly with byte arrays that may not be null terminated or contain non-printing characters.

    I have used narrow chars for convenience, but this idea will work with wide chars with a few edits.

    Note: edited on linux gcc so I have simulated the windows API.

    #include <cstdint>
    #include <utility>
    #include <iostream>
    #include <variant>
    
    #define WINAPI
    #define _In_opt_
    #define _Out_
    #define _In_
    
    struct _hmodule {};
    using HMODULE = _hmodule*;
    using LPTSTR = char*;
    using LPCTSTR = const char*;
    using DWORD = std::uint32_t;
    
    extern DWORD WINAPI GetModuleFileName(
      _In_opt_ HMODULE hModule,
      _Out_    LPTSTR  lpFilename,
      _In_     DWORD   nSize
    );
    
    extern WINAPI DWORD GetLastError();
    
    template<class Ret, class...Args>
    struct api_call_site
    {
        const char* file;
        int line;
        const char* current_function;
        const char* called_function;
        Ret (* function)(Args...);
    };
    
    template<class Ret, class...Args>
    auto make_api_call_site(const char* file, int line, const char* callername, const char* calleename, Ret (* WINAPI callee)(Args...))
    {
        return api_call_site<Ret, Args...>
        {
            file, 
            line,
            callername,
            calleename,
            callee
        };
    }
    
    template<class T>
    void emit_log(LPCTSTR& sep, std::ostream& os, T&& x)
    {
        os << sep << x;
        sep = ",";
    }
    
    template<class Ret>
    struct error_with_value
    {
        DWORD error;
        Ret value;
    
        bool has_error() const { return error != 0; }
        friend std::ostream& operator<<(std::ostream& os, const error_with_value& ewv)
        {
            os << "{ error: " << ewv.error << ", value: ";
            LPCTSTR sep = "";
            emit_log(sep, os, ewv.value);
            os << " }";
            return os;
        }
    };
    
    
    #define api(a) make_api_call_site(__FILE__, __LINE__, __func__, #a, a)
    
    
    // this will need some specialisations...
    void emit_log(LPCTSTR& sep, std::ostream& os, std::nullptr_t)
    {
        os << sep << "nullptr";
        sep = ",";
    }
    
    template<class Ret, class...Args, class...GivenArgs>
    auto api_call(api_call_site<Ret, Args...> const& callsite, GivenArgs&&...args) -> error_with_value<Ret>
    {
        // log call here
        std::clog << callsite.file << ":" << callsite.line << "@" << callsite.current_function << " - ";
        std::clog << "calling " << callsite.called_function << "(";
        // appropriate code to print arguments in a safe way here...
        LPCTSTR sep = "";
        using expand = int[];
        void(expand{0,
            (emit_log(sep, std::clog, args),0)...
        });
        std::clog << ")";
        error_with_value<Ret> result
        {
            0,
            callsite.function(std::forward<GivenArgs>(args)...)
        };
        result.error = GetLastError();
    
        std::clog << " -> returns: " << result;
        return result;
    }
    
    int main()
    {
        char buffer[255];
        DWORD bufsize = 255;
    
        auto result = api_call(api(GetModuleFileName), nullptr, buffer, bufsize);
        if (! result.has_error())
        {
            //
        }
    
    }
    

    example output:

    main.cpp:120@main - calling GetModuleFileName(nullptr,,255) -> returns: { error: 0, value: 14 }
    

    http://coliru.stacked-crooked.com/a/e5da55af212d5500

    How do I get the number of arguments in the API call?

    template<class Ret, class...Args>
    struct api_call_site
    {
        const char* file;
        int line;
        const char* current_function;
        const char* called_function;
        Ret (* function)(Args...);
    
        // like this
        static constexpr std::size_t nofArgs()
        {
            return sizeof...(Args);
        } 
    };