如果条件不满足,调用函数时会出现编译时错误

c++: compile time error on function call if condition not satisfied?

本文关键字:编译时错误 函数 条件 不满足 调用 如果      更新时间:2023-10-16

这是我想要实现的一个简化示例。因此,这可能看起来有点傻,但请耐心听我说。假设我有

template<int i> 
class Class1{
foo(){cout<<"j is divisible by i, so we will hang out"<<endl;}
}

和具有固定int j变量的class2:由这样的int模板化或具有成员变量。我希望class2实例只有在满足特定条件时才能调用foo(),在这种情况下,我想确保说(j%i==0)

我能想出的最好的办法是:

template<int i> 
class Class1{
    static const int intParam=i;
    template<bool true>
    foo(){cout<<"j is divisible by i, so we will hang out"<<endl;}
}

然后类2会这样调用它:

foo<class1::intParam%j>()

不太好。有更好的方法吗?我已经看到了'std::enable_if',这是相关的,但我不太确定。

如果你想要更大的图片,这是一个信号/委托代理机制。在系统中,只有当任务对象与任务中指定的角色枚举(int i)匹配时,执行对象才能够服务/请求任务对象。本质上,这应该是没有动态多态性的基于枚举的设计。在c++中是否有更好的方法来做到这一点?

使用static_assert:

template<int i, int j> 
class Class1
{
  public:
  void foo()
  {
    static_assert(i % j == 0, "Message upon i % j == 0 being false");
    cout<<"j is divisible by i, so we will hang out"<<endl;
  }
};

叫它e.g.

Class1<42, 24> f; 
f.foo();

更新注释,只需添加j作为foo()的额外模板参数,然后:

template<int i> 
class Class1
{
  public:
  template<int j>
  void foo()
  {
    static_assert(i % j == 0, "Message upon i % j == 0 being false");
    cout<<"j is divisible by i, so we will hang out"<<endl;
  }
};
int main() 
{
    Class1<42> f; 
    f.foo<24>();
    return 0;
}

与其他解决方案相反,我建议使用SFINAE根据条件启用或禁用功能。

template<int i> 
struct Class1 {
    template<int j, std::enable_if_t<i % j == 0>* = 0> 
    void foo() {
    }
};

这个解决方案的优点是,如果foo()有其他重载,编译器会尝试它们,而不是给出一个硬错误。正如您在这个实例中看到的,编译器给出的错误是:

main.cpp: In function 'int main()':
main.cpp:12:24: error: no matching function for call to 'Class1<3>::foo()'
    Class1<3>{}.foo<2>();
                       ^

这意味着错误发生在用户的代码中,而不是在你的头文件中,如果有一个替代函数在这个不起作用的时候,编译器会尝试其他的重载-

编辑

你真正要求的是让的代码是错误的,我很遗憾地说这是不可能的。我不认为有一个IDE可以防止你编写无效的代码。但是,如果您实现了下面我的解决方案之一(编译时的解决方案),那么一个足够先进的IDE将能够在您点击编译之前向您提供您编写的代码是坏的信息。

像Visual Studio这样的编译器本质上是"编译"的。在后台为你准备的东西,然后用红色的弯弯曲曲的线来强调不好的代码(读作:不会编译)。

编译时检查下面的答案

您为您的Class2结构提出了几种可能性,因此让我们逐一解决:

首先我们将从你的Class1开始基本上按照你的定义:

template<int i> 
struct Class1{
// how to define a "foo" function that is only callable 
// if Class2's j is evenly divisble by i?
};

第一种可能是Class2有一个模板化的j参数:

template<int j>
struct Class2
{
    //...
};
一个好的解决方案是模板Class1foo方法,然后包含一个static_assert,这是一个编译时断言。这是因为ij在编译时是已知的。

现在Class1看起来像这样:

template<int i> 
struct Class1{
   template<int j>
   void foo()
   {
       static_assert(j%i==0, "j is not evenly divisible by i");
       std::cout << "j is evenly divisble by i" << std::endl;
   }   
};

Class2可以这样调用foo:

template<int j>
struct Class2
{
    void CallFoo()
    {
        Class1<2> c1;
        c1.foo<j>(); // works
        
        //Class1<3> c2;
        //c2.foo<2>(); // fails static assert  
    }
};

演示

您提到的另一种可能性是Class2可能有j的成员变量。只要该成员变量是constexpr(结果也是static),就可以实现这一点:

struct Class2
{
    static constexpr int j = 4;
    void CallFoo()
    {
        Class1<2> c1;
        c1.foo<j>(); // works
        
        //Class1<3> c2;
        //c2.foo<2>(); // fails static assert 
    }
};

constexpr在这里定义了一个编译时常数。所以这个值在编译时是已知的,我们的static_assert可以正常工作。

如果j 不是 constexpr,那么我们无法实现编译时断言。此时,您被降级到运行时异常处理:

template<int i> 
struct Class1{
   void foo(int j)
   {
       if (j%i != 0)
          throw std::invalid_argument("j is not evenly divisible by i");
       std::cout << "j is evenly divisble by i" << std::endl;
   }   
};
struct Class2
{
    int j = 4;
    void CallFoo()
    {
        Class1<2> c1;
        c1.foo(j); // works
        j = 3;
        c1.foo(j); // throws
    }
};
演示