基于条件的远期变异论证

发布于 2025-02-09 20:56:58 字数 635 浏览 1 评论 0原文

我想实现以下行为:

enum class Fruit {
 Apple,
 Orange,
 Pear,
};

template<typename... Args>
void processFruit(Fruit fruit_type, Args&&... args) {
 switch (fruit_type) {
   case Apple:
     processApple(std::forward<Args>args...);
     break;
   case Orage:
     processOrange(std::forward<Args>(args)...);
     break;
   default:
     break;
 }
 postProcessing();
 return;
}

ProcessAppleprocessorange采取不同数量的参数。 当然,这不能编译。编译器无法基于switch语句实例化函数。我在static_if上找到了一些建议。只是想知道我的情况是否有更简单的方法。谢谢。

PS我的平台将我限制在C ++ 14解决方案上。

I would like to achieve following behavior:

enum class Fruit {
 Apple,
 Orange,
 Pear,
};

template<typename... Args>
void processFruit(Fruit fruit_type, Args&&... args) {
 switch (fruit_type) {
   case Apple:
     processApple(std::forward<Args>args...);
     break;
   case Orage:
     processOrange(std::forward<Args>(args)...);
     break;
   default:
     break;
 }
 postProcessing();
 return;
}

processApple and processOrange take different number of arguments.
This of course cannot compile. Compiler cannot instantiate functions based on the switch statement. I found some suggestions on static_if. Just wondering if there's a simpler way for my situation. Thanks.

p.s. my platform restricts me on C++14 solutions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

滿滿的愛 2025-02-16 20:56:58

您的问题会弄乱水域,因为您正在混合运行时和编译时间逻辑,因为在您的情况下,函数参数的数量和类型取决于某物的运行时值。

现在,您会遇到两个不同的选择:可以进行这项工作(即使在C ++ 14中),但是任何未通过正确参数的呼叫站点都将在运行时无法检测到。这样做的方法是借助std :: enable_if_texptype()std :: dectval。我已经为您的示例完成了此操作,并且在C ++ 14模式下为我编译了以下编译:(

#include <iostream>
#include <type_traits>
#include <stdexcept>

enum class Fruit {
Apple,
Orange,
Pear,
};

void processApple(int a, int b);
void processOrange(int c);
void processPear(double d);
void postProcessing();

struct Processor
{
    template<typename... Ts, typename = decltype(processApple(std::forward<Ts>(std::declval<Ts>())...))>
    static void process(std::integral_constant<Fruit, Fruit::Apple>, Ts&&... ts)
    {
        processApple(std::forward<Ts>(ts)...);
    }

    template<typename... Ts, typename = decltype(processOrange(std::forward<Ts>(std::declval<Ts>())...))>
    static void process(std::integral_constant<Fruit, Fruit::Orange>, Ts&&... ts)
    {
        processOrange(std::forward<Ts>(ts)...);
    }

    template<typename... Ts, typename = decltype(processPear(std::forward<Ts>(std::declval<Ts>())...))>
    static void process(std::integral_constant<Fruit, Fruit::Pear>, Ts&&... ts)
    {
        processPear(std::forward<Ts>(ts)...);
    }

    [[noreturn]] static void process(...)
    {
        // While this is inserted, it should never be called in practice,
        // unless there's a programming error.
        throw std::invalid_argument("Invalid argument specified to processFruit().");
    }
};

template<typename... Args>
void processFruit(Fruit fruit_type, Args&&... args) {
    switch (fruit_type) {
    case Fruit::Apple:
        Processor::process(std::integral_constant<Fruit, Fruit::Apple>(), std::forward<Args>(args)...);
        break;
    case Fruit::Orange:
        Processor::process(std::integral_constant<Fruit, Fruit::Orange>(), std::forward<Args>(args)...);
        break;
    case Fruit::Pear:
        Processor::process(std::integral_constant<Fruit, Fruit::Pear>(), std::forward<Args>(args)...);
        break;
    default:
        break;
    }
    postProcessing();
    return;
}

void processApple(int a, int b)
{
    std::cout << "Apple: " << a << " // " << b << std::endl;
}

void processOrange(int c)
{
    std::cout << "Orange: " << c << std::endl;
}

void processPear(double d)
{
    std::cout << "Pear: " << d << std::endl;
}

void postProcessing()
{
    std::cout << "Post processing" << std::endl;
}

int main()
{
    processFruit(Fruit::Apple, 4, 8);
    processFruit(Fruit::Orange, 15);
    processFruit(Fruit::Pear, 1.234);
    // The following will only throw an exception, but not fail to compile
    //processFruit(Fruit::Pear, 1.234, 77);
    return 0;
}

struct处理器只是为了清理名称空间,它们可能是全局函数。)这里的问题是编译器无法检测到错误的呼叫,请参阅processFruit的评论(fruat :: pear,1.234,77);会产生例外,但编译器无法检测到它在编译时。

在我看来,这不是很明智。由于参数取决于fruit无论如何类型,我真的看不到当fruit> fruit参数仅知道时,您什至无法执行这些功能之一的调用。运行时,因为每个呼叫站点仅适用于一种类型。

而且,如果您确实知道在编译时,可以通过超载以更简单的方式来完成这一点,这也允许更好地编译时间诊断:

#include <iostream>
#include <type_traits>

enum class Fruit {
Apple,
Orange,
Pear,
};

void processApple(int a, int b);
void processOrange(int c);
void processPear(double d);
void postProcessing();

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Apple>, Args&&... args)
{
    processApple(std::forward<Args>(args)...);
    // or put the code of processApple directly in here
}

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Orange>, Args&&... args)
{
    processOrange(std::forward<Args>(args)...);
    // or put the code of processOrange directly in here
}

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Pear>, Args&&... args)
{
    processPear(std::forward<Args>(args)...);
    // or put the code of processPear directly in here
}

template<Fruit f, typename... Args>
void processFruit2(Args&&... args)
{
    processFruit2Helper(std::integral_constant<Fruit, f>(), std::forward<Args>(args)...);
    postProcessing();
}

void processApple(int a, int b)
{
    std::cout << "Apple: " << a << " // " << b << std::endl;
}

void processOrange(int c)
{
    std::cout << "Orange: " << c << std::endl;
}

void processPear(double d)
{
    std::cout << "Pear: " << d << std::endl;
}

void postProcessing()
{
    std::cout << "Post processing" << std::endl;
}

int main()
{
    processFruit2<Fruit::Apple>(4, 8);
    processFruit2<Fruit::Orange>(15);
    processFruit2<Fruit::Pear>(1.234);

    // The following will fail to compile here (which is good)
    //processFruit2<Fruit::Pear>(1.234, 77);
    return 0;
}

这都说,我怀疑您的代码中存在一些更高级别的设计问题,但我们将无法理解您提供的有限示例。

Your question muddies the waters a bit, because you're mixing runtime and compile-time logic, because in your case the number and types of function arguments depends on the runtime value of something.

You're now stuck with two different options: it is possible to make this work (even in just C++14), but any call site that doesn't pass the correct arguments will not be detected until runtime. The way to do so is with the help of std::enable_if_t, decltype() and std::declval. I've done this for your example, and the following compiles for me in C++14 mode:

#include <iostream>
#include <type_traits>
#include <stdexcept>

enum class Fruit {
Apple,
Orange,
Pear,
};

void processApple(int a, int b);
void processOrange(int c);
void processPear(double d);
void postProcessing();

struct Processor
{
    template<typename... Ts, typename = decltype(processApple(std::forward<Ts>(std::declval<Ts>())...))>
    static void process(std::integral_constant<Fruit, Fruit::Apple>, Ts&&... ts)
    {
        processApple(std::forward<Ts>(ts)...);
    }

    template<typename... Ts, typename = decltype(processOrange(std::forward<Ts>(std::declval<Ts>())...))>
    static void process(std::integral_constant<Fruit, Fruit::Orange>, Ts&&... ts)
    {
        processOrange(std::forward<Ts>(ts)...);
    }

    template<typename... Ts, typename = decltype(processPear(std::forward<Ts>(std::declval<Ts>())...))>
    static void process(std::integral_constant<Fruit, Fruit::Pear>, Ts&&... ts)
    {
        processPear(std::forward<Ts>(ts)...);
    }

    [[noreturn]] static void process(...)
    {
        // While this is inserted, it should never be called in practice,
        // unless there's a programming error.
        throw std::invalid_argument("Invalid argument specified to processFruit().");
    }
};

template<typename... Args>
void processFruit(Fruit fruit_type, Args&&... args) {
    switch (fruit_type) {
    case Fruit::Apple:
        Processor::process(std::integral_constant<Fruit, Fruit::Apple>(), std::forward<Args>(args)...);
        break;
    case Fruit::Orange:
        Processor::process(std::integral_constant<Fruit, Fruit::Orange>(), std::forward<Args>(args)...);
        break;
    case Fruit::Pear:
        Processor::process(std::integral_constant<Fruit, Fruit::Pear>(), std::forward<Args>(args)...);
        break;
    default:
        break;
    }
    postProcessing();
    return;
}

void processApple(int a, int b)
{
    std::cout << "Apple: " << a << " // " << b << std::endl;
}

void processOrange(int c)
{
    std::cout << "Orange: " << c << std::endl;
}

void processPear(double d)
{
    std::cout << "Pear: " << d << std::endl;
}

void postProcessing()
{
    std::cout << "Post processing" << std::endl;
}

int main()
{
    processFruit(Fruit::Apple, 4, 8);
    processFruit(Fruit::Orange, 15);
    processFruit(Fruit::Pear, 1.234);
    // The following will only throw an exception, but not fail to compile
    //processFruit(Fruit::Pear, 1.234, 77);
    return 0;
}

(The struct Processor is just to clean up the namespace, they could be global functions.) Problem here is that the compiler can't detect a wrong call, see the commented out call to processFruit(Fruit::Pear, 1.234, 77); that would generate an exception, but the compiler couldn't detect it at compile time.

In my eyes this is not very sensible though. Since the arguments depend on the Fruit type anyway, I don't really see how you could even perform a call to one of these functions when the Fruit argument is only known at runtime, because each call site will only work for a single type.

And if you do know that at compile time anyway, this could be done in a much simpler way via overloads, that also allows for much better compile-time diagnostics:

#include <iostream>
#include <type_traits>

enum class Fruit {
Apple,
Orange,
Pear,
};

void processApple(int a, int b);
void processOrange(int c);
void processPear(double d);
void postProcessing();

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Apple>, Args&&... args)
{
    processApple(std::forward<Args>(args)...);
    // or put the code of processApple directly in here
}

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Orange>, Args&&... args)
{
    processOrange(std::forward<Args>(args)...);
    // or put the code of processOrange directly in here
}

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Pear>, Args&&... args)
{
    processPear(std::forward<Args>(args)...);
    // or put the code of processPear directly in here
}

template<Fruit f, typename... Args>
void processFruit2(Args&&... args)
{
    processFruit2Helper(std::integral_constant<Fruit, f>(), std::forward<Args>(args)...);
    postProcessing();
}

void processApple(int a, int b)
{
    std::cout << "Apple: " << a << " // " << b << std::endl;
}

void processOrange(int c)
{
    std::cout << "Orange: " << c << std::endl;
}

void processPear(double d)
{
    std::cout << "Pear: " << d << std::endl;
}

void postProcessing()
{
    std::cout << "Post processing" << std::endl;
}

int main()
{
    processFruit2<Fruit::Apple>(4, 8);
    processFruit2<Fruit::Orange>(15);
    processFruit2<Fruit::Pear>(1.234);

    // The following will fail to compile here (which is good)
    //processFruit2<Fruit::Pear>(1.234, 77);
    return 0;
}

That all said, I suspect there's some higher-level design problem in your code, but we won't be able to understand that with the limited example you've provided.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文