LoginSignup
0
1

C++20におけるテンプレートパラメータの制約方法の比較

Last updated at Posted at 2024-04-20

はじめに

C++でテンプレートパラメータを制約する方法は複数ある。制約を外れたパラメータを指定すればコンパイルエラーになる訳だが、その違いについて簡単に検証してみた。

環境

  • WSL2 上で g++ を用いてコンパイル(g++のバージョンは末尾に記載)
  • C++20 を使用

今回の例題

「テンプレートパラメータを4byteの算術型整数に限定する」
(例えば、uint32_tはコンパイル通すがcharやオブジェクト型は通さない)

以下に示すテンプレート関数でパラメータの制約を行う

template <typename T> void func()
{
    T n = 0xFFFFFFFF;
    std::cout << n << std::endl;
}

呼び出しは、

struct object
{
    int i;
};
    
int main(int argc, char* argv[])
{
    func<char>();      // NG: char は 1 byte
    func<object>();    // NG: object はクラス型
    return 0;
}

とし、不正なパラメータを指定することでどんな風にコンパイルエラーが出るかを比較してみる。

注意

  • 要点のみ説明するため、#include文などは省略しています
  • コンパイルエラー内のソースコードの位置は無視して下さい

Case1 : requires を使う

template <typename T>
requires (sizeof(T) == 4 && std::is_integral_v<T>) void func()
{
    Tn = 0xFFFFFFFF;
    std::cout << n << std::endl;
}
コンパイル結果
test.cpp: In function ‘int main(int, char**)’:
test.cpp:58:13: error: use of function ‘void func() [with T = char]’ with unsatisfied constraints
   58 |  func<char>();
      |             ^
test.cpp:24:81: note: declared here
   24 | template <typename T> requires (sizeof(T) == 4  &&  std::is_integral_v<T>) void func()
      |                                                                                 ^~~~
test.cpp:24:81: note: constraints not satisfied
test.cpp: In instantiation of ‘void func() [with T = char]’:
test.cpp:58:13:   required from here
test.cpp:24:81:   required by the constraints of ‘template<class T>  requires  sizeof (T) == 4 && (is_integral_v<T>) void func()’
test.cpp:24:43: note: the expression ‘sizeof (T) == 4 [with T = char]’ evaluated to ‘false’
   24 | template <typename T> requires (sizeof(T) == 4  &&  std::is_integral_v<T>) void func()
      |                                 ~~~~~~~~~~^~~~
test.cpp:59:15: error: use of function ‘void func() [with T = object]’ with unsatisfied constraints
   59 |  func<object>();
      |               ^
test.cpp:24:81: note: declared here
   24 | template <typename T> requires (sizeof(T) == 4  &&  std::is_integral_v<T>) void func()
      |                                                                                 ^~~~
test.cpp:24:81: note: constraints not satisfied
test.cpp: In instantiation of ‘void func() [with T = object]’:
test.cpp:59:15:   required from here
test.cpp:24:81:   required by the constraints of ‘template<class T>  requires  sizeof (T) == 4 && (is_integral_v<T>) void func()’
test.cpp:24:58: note: the expression ‘is_integral_v<T> [with T = object]’ evaluated to ‘false’
   24 | template <typename T> requires (sizeof(T) == 4  &&  std::is_integral_v<T>) void func()
      |                                                     ~~~~~^~~~~~~~~~~~~~~~

出力は27行。
要点を把握するためにgreperror:行のみ抽出してみる

test.cpp:58:13: error: use of function ‘void func() [with T = char]’ with unsatisfied constraints
test.cpp:59:15: error: use of function ‘void func() [with T = object]’ with unsatisfied constraints

charやクラス型のobjectを指定しているのがまずいと教えてくれているし、どこでその不正な指定をしているかも示されている。

Case2: concept を使う

template <typename T>
concept Con = sizeof(T) == 4 && std::is_integral_v<T>;

template <Con T> void func()
{
    Tn=0xFFFFFFFF;
    std::cout << n << std::endl;
}
コンパイル結果
test.cpp: In function ‘int main(int, char**)’:
test.cpp:66:13: error: use of function ‘void func() [with T = char]’ with unsatisfied constraints
   66 |  func<char>();
      |             ^
test.cpp:35:23: note: declared here
   35 | template <Con T> void func()
      |                       ^~~~
test.cpp:35:23: note: constraints not satisfied
test.cpp: In instantiation of ‘void func() [with T = char]’:
test.cpp:66:13:   required from here
test.cpp:33:9:   required for the satisfaction of ‘Con<T>’ [with T = char]
test.cpp:33:25: note: the expression ‘sizeof (T) == 4 [with T = char]’ evaluated to ‘false’
   33 | concept Con = sizeof(T) == 4  &&  std::is_integral_v<T>;
      |               ~~~~~~~~~~^~~~
test.cpp:68:15: error: use of function ‘void func() [with T = object]’ with unsatisfied constraints
   68 |  func<object>();
      |               ^
test.cpp:35:23: note: declared here
   35 | template <Con T> void func()
      |                       ^~~~
test.cpp:35:23: note: constraints not satisfied
test.cpp: In instantiation of ‘void func() [with T = object]’:
test.cpp:68:15:   required from here
test.cpp:33:9:   required for the satisfaction of ‘Con<T>’ [with T = object]
test.cpp:33:40: note: the expression ‘is_integral_v<T> [with T = object]’ evaluated to ‘false’
   33 | concept Con = sizeof(T) == 4  &&  std::is_integral_v<T>;
      |                                   ~~~~~^~~~~~~~~~~~~~~~

出力はrequiresを使った時と同じく27行、内容もほぼ同じ。

g++ --std=c++20 test.cpp |& grep error:
test.cpp:66:13: error: use of function ‘void func() [with T = char]’ with unsatisfied constraints
test.cpp:68:15: error: use of function ‘void func() [with T = object]’ with unsatisfied constraints

これもrequiresを使った時と同じ。修正すべき行が示されているので修正しやすい。

コンセプトを定義をする分少しだけ手間がかかるが、同じ制約を何度も用いるならこちらの方がよいだろう。

Case3: static_assert を使う

template <typename T> void func()
{
    static_assert(sizeof(T) == 4);
    static_assert(std::is_integral_v<T>);
    T n = 0xFFFFFFFF;
    std::cout << n << std::endl;
}

直感的な記述で簡単。
正しいコードを書けば何の問題もないのだが、不正なテンプレートパラメータを指定すると、、、

コンパイル結果
test.cpp: In instantiation of ‘void func1() [with T = char]’:
test.cpp:52:14:   required from here
test.cpp:16:26: error: static assertion failed
   16 |  static_assert(sizeof(T) == 4);
      |                ~~~~~~~~~~^~~~
test.cpp:19:8: warning: overflow in conversion from ‘unsigned int’ to ‘char’ changes value from ‘4294967295’ to ‘'\37777777777'’ [-Woverflow]
   19 |  T n = 0xFFFFFFFF;
      |        ^~~~~~~~~~
test.cpp: In instantiation of ‘void func1() [with T = object]’:
test.cpp:55:16:   required from here
test.cpp:17:21: error: static assertion failed
   17 |  static_assert(std::is_integral_v<T>);
      |                ~~~~~^~~~~~~~~~~~~~~~
test.cpp:19:8: error: conversion from ‘unsigned int’ to non-scalar type ‘object’ requested
   19 |  T n = 0xFFFFFFFF;
      |        ^~~~~~~~~~
test.cpp:20:12: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘object’)
   20 |  std::cout << n << std::endl;
      |  ~~~~~~~~~~^~~~
In file included from /usr/include/c++/10/iostream:39,
                 from test.cpp:1:
/usr/include/c++/10/ostream:108:7: note: candidate: ‘std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]’
  108 |       operator<<(__ostream_type& (*__pf)(__ostream_type&))
      |       ^~~~~~~~

(以下省略)

出力410行、、、 こんなの読めたもんじゃない、、、
grepで要約してみても、

test.cpp:16:26: error: static assertion failed
test.cpp:17:21: error: static assertion failed
test.cpp:19:8: error: conversion from ‘unsigned int’ to non-scalar type ‘object’ requested
test.cpp:20:12: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘object’)
/usr/include/c++/10/system_error:262:5: note: candidate: ‘template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::error_code&)’
/usr/include/c++/10/system_error:262:5: note:   template argument deduction/substitution failed:
/usr/include/c++/10/ostream:773:5: error: no type named ‘type’ in ‘struct std::enable_if<false, std::basic_ostream<char>&>’

static_assertが失敗したのはわかるが、これだけではどこのテンプレート関数呼び出しが間違っているのかさっぱりわからない。結局膨大なエラー出力を読み解いていかないといけない。

ちなみにこのエラーでいう16~20行はfunc()を書いている部分であり、呼び出し元ではない。

まとめ

static_assertは文法が直感的で書くのは簡単だが、山のようにコンパイルエラーは吐いたりする。C++20から導入されたconceptは記述の仕方にちょっとクセがあって理解しにくいが、コンパイルエラーもわかりやすくなるので、コーディングの省力化になるかな。

今回使用した g++ のバージョン情報

g++ --version
g++ (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1