【问题标题】:Extract types from std::tuple for a method signature从 std::tuple 中提取类型以获得方法签名
【发布时间】:2020-08-12 00:54:27
【问题描述】:

我正在寻找一种方法来提取std::tuple 的类型以定义方法签名。举以下(人为的)例子:

template <typename RetT, typename... ArgsT>
class A
{
public:
    typedef RetT ReturnType;
    typedef std::tuple<ArgsT...> ArgTypes;

    RetT doSomething(ArgsT... args)
    {
        // Doesn't make much sense, but it's just an example
        return (RetT) printf(args...);
    }
};

template <typename Enable, typename RetT, typename... ArgsT>
class AAdapter;

// Simply pass arguments along as-is
template <typename RetT, typename... ArgsT>
class AAdapter<std::enable_if_t<!std::is_same_v<RetT, float>>, RetT, ArgsT...> : public A<RetT, ArgsT...> {};

// Add additional first argument if RetT is float
template <typename RetT, typename... ArgsT>
class AAdapter<std::enable_if_t<std::is_same_v<RetT, float>>, RetT, ArgsT...> : public A<RetT, const char*, ArgsT...> {};



template <typename RetT, typename... ArgsT>
class B
{
public:
    typedef AAdapter<void, RetT, ArgsT...> AAdapter;

    // This needs to have the same method signature (return type and argument types) as AAdapter::doSomething()
    template <size_t... Index>
    typename AAdapter::ReturnType doSomething (
        typename std::tuple_element<Index, typename AAdapter::ArgTypes>::type... args
    ) {
        return a.doSomething(args...);
    }

public:
    AAdapter a;
};


int main(int argc, char** argv)
{
    // I would like to be able to remove the <0,1,2> and <0,1,2,3> below.
    B<int, const char*, int, int> b1;
    b1.doSomething<0,1,2>("Two values: %d, %d\n", 1, 2);

    B<float, const char*, int, int> b2;
    b2.doSomething<0,1,2,3>("Three values: %s, %d, %d\n", "a string", 1, 2);

    return 0;
}

考虑 AAdapter 更改、添加或删除不透明参数类型的方式。基本上,我希望B::doSomething() 简单地重定向到B::AAdapter::doSomething(),所以我希望这两种方法都具有完全相同的签名。问题是:如何从B 内部获取B::AAdapter::doSomething() 的参数类型?

我在上面的代码中对B::doSomething() 的定义是我所了解的最远的:我正在使用A 中的参数类型定义std::tuple,因此我可以将它们解压缩回参数包B。不幸的是,通过上述方法,我仍然需要在调用B::doSomething() 时手动提供Index... 模板参数。肯定有办法让这些Index... 参数自动从元组的大小中推导出来。我已经考虑过使用std::make_integer_sequence 的方法,但这需要我为序列本身定义一个额外的方法参数(并且它不能是具有默认值的最后一个参数,因为在参数包之后不允许使用其他参数) .

有没有什么方法可以做到这一点,不管有没有 std::tuple?需要 C++17 的解决方案就可以了。

编辑 1:

我现在意识到,我可以通过从 AAdapter 获得 B inherit 而不是将 AAdapter 对象作为成员来规避我的特定应用程序中的问题,但我仍然会想知道如何解决问题而不必这样做。

编辑 2:

也许有一些关于为什么AAdapter 存在以及我想要实现什么的额外信息。我正在围绕现有的 C API 实现一种包装类,实际上需要在另一个进程中调用,RPC 样式。因此,如果用户想在远程进程中调用 C 函数,他们将改为在本地调用我的包装类中的相应方法,该方法处理所有 RPC 内容,如类型转换、实际远程调用和其他丑陋的细节。这个包装类在我上面的代码中由B 表示。现在我的包装方法签名通常不会具有与 C 函数完全相同的签名。例如,包装器可能具有std::string_view,而不是C 函数具有的一对const char*, size_t。由于这里不重要的原因,它还需要有一个输出参数(一个指针),而不是 C 函数有一个返回值。

为了让我不必定义两个单独的方法签名(实际上是三个)并编写代码来转换每个单独的参数,我只将其中一个签名作为模板参数 RetT, ArgsT... 传递给B。签名转换类(在上面的示例中为AAdapter)然后应用规则,如何通过添加参数、更改它们的类型等从第一个签名自动生成第二个签名。A 然后将保存这个生成的签名,并且B 将拥有我最初提供的那个。但是,我希望B 提供带有A 签名的invoke() 方法,从而将A 和整个方法签名完全隐藏在用户面前。这就是为什么我需要从B 中访问A 的模板参数类型,以及为什么我不能简单地删除中间类AAdapter

【问题讨论】:

  • 您可以使用std::tuple_element_t&lt;index, tuple_type&gt; 获取类tuple_type 中的索引类型。
  • @Anonymous1847 这就是我在示例中尝试执行的操作,但我需要访问所有元素,而不仅仅是具有特定索引的元素,并在调用者无需指定的情况下生成此索引列表手动操作基本上就是我要问的问题。

标签: c++ c++17 template-meta-programming stdtuple parameter-pack


【解决方案1】:

问题的核心是将元组变成参数包。

也许元组类型不是模板参数?在这种情况下,通过继承有一个简单的解决方案:

#include <vector>
#include <iostream>
#include <tuple>

template<typename... Types>
struct BImpl{
    typedef std::tuple<std::vector<Types>...> tuple_type;
    // maybe you will get a tuple type from some class templates. assume the 'tuple_type' is the result.
    // requirement: 'tuple_type' = std::tuple<SomeTypes...>
    // requirement: 'tuple_type' can be deduced definitely from template arguments 'Types...'.
    template<typename> // you can add other template arguments, even another std::tuple.
    struct OptCallHelper;
    template<typename... Args>
    struct OptCallHelper<std::tuple<Args...>>{
        auto dosomething(Args&&... args) /* const? noexcept? */{
            // do what you want...
            // requirement: you can definitely define the 'dosomething' here without any other informations.
            std::cout << "implement it here." << std::endl;
        }
    };
    typedef OptCallHelper<tuple_type> OptCall;
};

template<typename... Types>
struct B : private BImpl<Types...>::OptCall{
    typedef typename BImpl<Types...>::OptCall base;
    using base::dosomething;
    // obviously, you can't change the implementation here.
    // in other words, the definition of 'dosomething' can only depend on template arguments 'Types...'.
};


int main(){
    B<int, float> b;
    b({}, {}); // shows "implement it here."
    return 0;
}

您可以在BImpl 中做您想做的事情,然后改用B

   // This needs to have the same method signature (return type and argument types) as AAdapter::doSomething()
   template <size_t... Index>
   typename AAdapter::ReturnType doSomething (
       typename std::tuple_element<Index, typename AAdapter::ArgTypes>::type... args
   ) {
       return a.doSomething(args...);
   }

对于AAdaptor,我想你只是想要Adosomething的接口,你可以推导出来:

#include <iostream>

template<typename...>
struct AAdaptor{
    int dosomething(){
        std::cout << "???" << std::endl;
        return 0;
    }
};
// ignore the implementation of AAdaptor and A.
// just consider of how to get the interface of 'dosomething'.

template<typename... Types>
struct BImpl{
    typedef AAdaptor<Types...> value_type;
    typedef decltype(&value_type::dosomething) function_type;
    // attention: it won't work if 'AAdaptor::dosomething' is function template or overloaded.
    //            in this case, you should let A or AAdaptor give a lot of tuples to declare 'dosomething', referring to the first solution.

    

    template<typename>
    struct OptCallHelper;
    template<typename Ret, typename Klass, typename... Args>
    struct OptCallHelper<Ret(Klass::*)(Args...)>{
        value_type data;
        Ret dosomething(Args... args){
            return data.dosomething(args...);
        }
    };
    // attention: 'Ret(Klass::*)(Args...)' is different from 'Ret(Klass::*)(Args...) const', 'noexcept' as well in C++17.
    //            even Ret(Klass::*)(Args..., ...) is also different from them.
    //            you have to specialize all of them.

    typedef OptCallHelper<function_type> OptCall;
};

template<typename... Types>
struct B : BImpl<Types...>::OptCall{
    typedef typename BImpl<Types...>::OptCall base;
    using base::dosomething;
};


int main(){
    B<int, float> b;
    b(); // shows "???"
    return 0;
}

如果此代码与您的要求之间存在一些差异,请尝试给出另一个示例来暗示您的一些实现。目前还不清楚B 得到什么以及应该做什么。

【讨论】:

  • OptCallHelper 的东西似乎是一个很好的解决方案。这有点类似于我后来决定的,那就是:不要试图在B 中推导出doSomething(),而只是让B 继承自A(通过AAdapter),因此直接继承A::doSomething()。如果从A 直接继承“太多”,这个答案可能是更好的解决方案。 AAdapter 的目的仍然有点不同:它将负责在您的第一个 sn-p 中确定构成 BImpl::tuple_type 的类型,但您的解决方案可以通过简单地将它放在 B 和 @ 之间来调整987654341@我想。
【解决方案2】:

这演示了如何从元组中获取具有参数类型的函数:

#include <iostream>
#include <tuple>
#include <utility>

template <
        typename ArgTuple
>
class B_Class {};

template <typename... ArgTypes>
class B_Class<std::tuple<ArgTypes...> > {
public:
        static void b(
                ArgTypes...
        ) {
                std::cout << "successful call" << std::endl;
        }
};

int main() {
        using ArgTypes = std::tuple<int, char, float, double>;
        int i; char c; float f; double d;
        B_Class<ArgTypes>::b(i, c, f, d);
}

这会在运行时编译并打印“成功调用”。

【讨论】:

  • 这不会在 VS2019 上编译(“'Ints': is not a member of 'std::integer_sequence'”)。我不认为std::integer_sequence::Ints 存在,这有点问题。 Intsstd::integer_sequence 的模板参数(包),但它不应该作为类型从外部访问,因为您不能 typedef 参数包。毕竟,如果这是可能的,我可以对A 中的ArgsT... 执行完全相同的操作,只需使用typename AAdapter::ArgsT... 作为参数类型。尽管这在AAdapterAArgsT... 之间是不明确的。
  • @Alemarius Nexus 我的错,我在看en.cppreference.com/w/cpp/utility/integer_sequence,我在那里看到了“Ints”,也许我认为它在成员之下。无论如何,看看 std::integer_sequence 是否可以帮助你。
  • @Alemarius Nexus 我用适合我的东西编辑了我的答案。
  • 我认为您没有正确理解我的问题。您的实现完全删除了中间类 AAdapter,这对于我在真实代码中尝试执行的操作至关重要(当然,这只是一个精简示例)。 AAdapter 的第二个模板特化AArgsT... 添加了一个附加参数,B 不知道该参数,您的答案中缺少该参数。由AAdapter 而不仅仅是B 本身添加此参数也很重要。我将编辑我的问题,以便更深入地了解我为什么要这样做。
  • 问题的核心是将元组转换为参数包。您可以将更多模板参数添加到 B_Class 特化中,并让它专门用于 AAdapter 而不是 std::tuple
猜你喜欢
  • 1970-01-01
  • 2019-12-22
  • 1970-01-01
  • 1970-01-01
  • 2019-06-15
  • 2021-10-16
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
相关资源
最近更新 更多