C 传递序列上范围为变异功能模板

c++ passing sequence upper range to variadic function template

本文关键字:变异 功能 范围      更新时间:2023-10-16

我在模板的帮助下以特定方式调用API,并且我在传递恒定参数的情况下留下了一个问题。

我的尝试 int绑定

    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;
    }

主要:

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

如何解决此问题?

现在我的解决方法:

err = f3(api(getmodulefilename),rval,nullptr,path,max_path,l" exe-path");

f3是具有3个参数的API的专业化,因为在这种情况下,我现在无法传递到上限-4 - 生成一个序列:&lt; 1,2,3>。需要此序列调用带有3个参数的API,其中Tupel以F3()的参数RVAL开始。

背景:

API是#Define

F3调用API。

f3在序列/tupel的0位置上处理API的返回值。

f3带有所有参数调用另一个用于记录调试信息的变量函数。

一个tupel和两个函数调用的两个序列。

问题:

我想传递一个参数以控制tupel-size不给出的序列的上限,而是通过API函数签名。

我只需要一个fn(),而不是f0(),f1(),f2(),f3(),f3().....对于具有0、1、2、3 ...参数的API。

我想要这样的东西:

err = fn(seq3&lt; 4>,api(getmodulefilename),rval,nullptr,path,path,max_path,l" exe-path")

这是我的工作代码:

#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; }

预先感谢。

P.S。我正在使用Microsoft Visual Studio 2015

更新:

我尝试在Richard Hodges解决方案中关注模板API_CALL。

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()>{}); 

备注:

seq1&lt; 3> = seq_1&lt; 0,1,2>

callite.nofarg()= 3

如何获得正确数量的参数?

还不清楚如何处理错误等。我假设返回错误代码和值的元组。

这是我认为您想要的一般模式。您需要仔细考虑emit_log的专业和过载,尤其是可能未终止或包含非打印字符的字节数组。

为了方便起见,我使用了狭窄的字符,但是这个想法将与一些编辑一起使用。

注意:在Linux GCC上进行了编辑,因此我模拟了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())
    {
        //
    }
}

示例输出:

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

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

如何在API调用中获得参数数量?

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);
    } 
};

感谢理查德·霍奇斯(Richard Hodges)我可以解决我的问题:

现在,每个API都有一个API_CALL,并且API_CALL背后的模板确定了所谓的API的签名。比我要求的好得多。

#include <windows.h>
#include <atlstr.h>
#include <tuple>
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...> {
    // this sequence is more complicated in my real code, because
    // there are more variables for logging, but not for api calling     
    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;
// according to the solution from Richard Hodges
// *********************************************
template<typename Ret, typename...Args>   
struct api_call_site
{
    const CString file;
    int line;
    const CString Caller;
    const CString f_name;
    Ret(WINAPI* function)(Args...);     
    static constexpr std::size_t nofArgs() {
        return sizeof...(Args);
    }
};
template<typename Ret, typename...Args>
auto make_api_call_site(const CString file, int line, const CString Caller, const CString f_name, Ret(WINAPI* callee)(Args...))
// WINAPI see also here  https://stackoverflow.com/questions/18912931/why-need-to-use-winapi-for-the-syntax-for-declaring-function-pointers-for-fun
{
    return api_call_site<Ret, Args...>
    {
        file,
            line,
            Caller,
            f_name,
            callee
    };
}
template <typename Ret, typename...Args, typename TUP, int...INDICES3, int...INDICES1>
int fn(api_call_site<Ret, Args...> const& callsite, TUP tup, seq_3<INDICES3...>, seq_1<INDICES1...>) {
    int err = 0;
    // handling of return value from api call goes always in position 0 from tuple 
    std::get<0>(tup) = callsite.function(std::get<INDICES3>(tup) ...);
    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 Ret, typename...Args, typename...GivenArgs>
int api_call(api_call_site<Ret, Args...> const& callsite, GivenArgs&&...args) 
{
    int err;
    err = fn(callsite, std::tuple<GivenArgs...>(args...), seq3 <callsite.nofArgs()+1> {}, seq1 <sizeof...(GivenArgs)> {});
    return err;
}
int main() {
    DWORD       size_path = 20;   // make it small and get error 122 
    wchar_t     path[MAX_PATH];   // ERROR_INSUFFICIENT_BUFFER
    DWORD       rval = 0;
    int         err = 0;
    CString     tolog1(L"EXE-Path determined"); 
    int         tolog2 = 25; 
    // old way without logging information 
    rval = GetModuleFileName(nullptr, path, MAX_PATH);
    err = GetLastError();
    // new way with logging any variables ... behind the must variables for the api  
    // ****************************************************************************
#define api(a) make_api_call_site(__FILE__, __LINE__, __func__, L#a, a)
    err = api_call(api(GetModuleFileName), rval, nullptr, path, size_path, tolog1, tolog2); 
    return 0;
}