允许 C++20

#include<iostream>
using namespace std;

// simple case, CTAD works
template<class T>
struct A {
  A(const T&, int i =0) { cout << "i=" << i << "\n"; }
};


// now I want replace `i` in `A` constructor with an compile time constant - enable e.g. usage of `if constexpr(i==5)`.
// I try to do this via second non-type template parameter.
template<class T, int X>
struct B {
  B(const T&, int i =X) { cout << "X=" << X << "\n"; }
};

int main(void)
{
  long whatever =0;

  A a1{whatever};       // CTAD works
  A a2{whatever, 5};    // CTAD works

  B<long, 2> b1{whatever};    // explicit specified template arguments works
  B b2{whatever, 2};          // cannot deduce template arguments for 'B'

  return 0;
}

可以推断非类型模板参数,我的一本书(C++17完整指南)展示了一个例子:

template<typename T, int SZ>
class MyClass {
  public:
    MyClass(T(&)[SZ]) {}
};
MyClass mc("hello");  // deduces T as const char and SZ as 6

但我失败了…我们没有 constexpr 函数参数,对吗?

7

  • 3
    它无法推断,X因为没有任何东西可以推断它。请注意,X是的默认X,但它们不必相同。您无法X通过查看来推断i


    – 

  • 1
    书中示例的技巧之所以有效,是因为该SZ值来自类型信息(即数组的固定大小),并且可以用作 constexpr。但是,i构造函数中的参数B是方法参数,不能用作 constexpr。


    – 


  • 那么事实上非类型模板参数不能被推断吗?


    – 


  • 4
    在这种情况下不是。它需要处于编译时常量的上下文中。您可以使用例如std::integral_constantX从常量的值中推断出来。


    – 

  • “允许 C++20”并将其标记为 C++17是什么意思?


    – 


最佳答案
3

而且我们没有 constexpr 函数参数,对吧?

我们没有 constexpr 参数。

但是我们可能具有以下 NTTP 类型:

所以你可能会

template<class T, int X>
struct B {
  B(const T& t) : B(t, std::integral_constant<int, X>{}) {}
  B(const T& t, std::integral_constant<int, X>) { // be part of CTAD
     std::cout << "X=" << X << "\n";
  }
};

template <int N>
using ic = std::integral_constant<int, N>;

int main()
{
    B<long, 2> b1{42L}; // explicit specified template arguments works
    B b2{42L, std::integral_constant<int, 2>{}};
    B b3{42L, ic<42>{}};
}

3

  • 很酷,std::integral_constant 只是让编译器满意的辅助工具,它甚至没有在 Bs 构造函数中被触及(可能完全被优化掉了)。我得到了类似部分 CTAG 的东西。只要推导出 T,就可以忍受。


    – 

  • 1
    否则,还是老办法:template <int N, typename T> B<T, N> make_B(const T&);auto b = make_B<2>(42L);


    – 

  • 哦,我喜欢这种老方法!直接,没有弯路。装在小宏中,准备就绪。


    – 

不幸的是,从 C++23 开始,您不能有constexpr函数参数(有迹象表明这在未来可能会成为可能,请参阅)。为了使非类型参数推导有意义,您需要有一个可以从中推导的编译时值。但由于您不能有编译时(constexpr)函数参数,您不能有可以从中推导的编译时值,因此您不能从非类型函数参数中进行非类型参数推导。

然而,正如 Fedor Pikus 所说:“在 C++ 中,如果你不能做某事,但你真的很想做,那么你还是可以做到的”。在这种情况下,你需要将编译时值包装到编译时类型中,因为编译时类型可用于参数推导。我认为最简单的方法是使用 lambda。例如:

#include <iostream>
#include <type_traits>

template <class T>
struct B
{
    B(const T&, auto c)
    requires(std::is_invocable_r_v<int, decltype(c)>) {
        std::cout << "X=" << c() << "\n";
    }
};

int main() {
    long whatever = 0;
    B b{ whatever, []{ return 2; } };

    return 0;
}

如果你想独辟蹊径,你甚至可以使用宏:

#define CreateB(Arg1, Arg2) B{Arg1, []{ return Arg2; }}

int main() {
    long whatever = 0;
    auto b = CreateB(whatever, 2);

    return 0;
}

此外,根据用例,您可能无论如何都需要constexpr int在其他类型中包含该值。在这种情况下,您可以使用该类型来推断constexpr值:

#include <iostream>

template <int X>
struct MyType
{
    // do stuff with X here ...
};

template <template <int> class T, int X>
struct B
{
    // deducing both T and X from function argument
    B(const T<X>&) {
        std::cout << "X=" << X << "\n";
    }
};

int main() {
    MyType<2> whatever{};
    B b{whatever};

    return 0;
}

但是是的,到目前为止,这已经是最接近的了。我知道这可能很烦人。希望未来的 C++ 版本能够实现这一点。另一方面,正如 @cigien 指出的那样,这也可能永远不会发生。

6

  • “有迹象表明这将在 C++26 中实现”嗯!有提案吗


    – 

  • 2


    – 

  • constexpr-parameters 提案之前曾被提出过,但据我了解,在常量表达式中使用参数存在根本问题,因此不太可能成为标准。


    – 


  • 我从委员会相关人员那里听说,他们甚至在讨论 C++26。如果是这样,那应该很快就会知道。但你可能是对的,这可能永远不会发生。我会修改答案的这一部分以采取更保守的立场。


    – 

  • @HolyBlackCat akastd::constant_wrapper正在针对 C++26 进行讨论,并且根据 GitHub 上的帖子,它似乎“仍在讨论中”


    – 


使用 Jarod42s 的“老方法”,我在这里结束

template<class T, int X>
struct B {
  B(const T&) { cout << "X=" << X << "\n"; }
};

template <int N, class T> B<T, N> make_B(const T& t) { return B<T, N>(t); }
#define MAKE_B(obj, val) make_B<val>(obj)

...

B b2 =MAKE_B("Whatever", 42);