Shell Metacharacters
Shell script 에서는 프로그래밍 언어에서처럼 여러가지 연산자를 제공하지 않습니다. 가령 산술연산을 위해서는 별도로 제공되는 산술확장 이나 명령 을 이용하거나 외부 명령을 사용합니다. 하지만 명령문을 작성할 때 사용되는 메타문자들이 있습니다. 이 메타문자들은 명령문 상에서 특별한 기능을 하므로 동일한 문자가 명령문의 스트링에 포함될 경우 반드시 escape 하거나 quote 해야 오류가 발생하지 않습니다.
C++ 소스코드를 생성해주는 템플릿 프로그래밍을 메타프로그래밍 이라고 하죠, 또한 Makefile 을 해당 플랫폼에 맞게 생성해주는 cmake 같은 프로그램을 메타 make 이라고 합니다. regex 에서 사용되는
*
,?
,[ ]
와 같은 메타문자나 shell 에서 사용되는 메타문자들은 실제 실행되는 명령문을 생성하는 과정에서 특별한 기능을 수행합니다.
( ) ` | & ;
&& || # AND, OR 문자
< > >> # redirection 문자
* ? [ ] # glob 문자
" ' # quotes 문자
\ $
= += # 대입연산
( )
- 함수를 작성할 때
- subshell 을 생성할 때
- 명령 grouping 에 사용
프로그래밍 언어에서처럼 공백이나 ;
에 대한 제약 없이 자유롭게 사용할 수 있는 메타문자입니다.
`
backtick 문자는 $( )
와 함께 명령 치환에 사용됩니다.
&& ||
AND, OR 논리 메타문자.
이것은 shell 메타문자로 [[ ]]
특수 명령에서 제공하는 연산자와는 다른 것입니다.
shell 에서 &&
||
메타문자는 우선순위를 같게 취급합니다.
&
명령을 background 로 실행할 때 사용합니다.
또한 ;
와 동일하게 명령문의 종료를 나타내므로 명령을 한줄에 연이어 쓸경우 ;
를 붙여서는 안됩니다.
command1 &; command2 & # error
command1 & command2 & # OK
{ ... command & ;} # error
{ ... command & } # OK
|
명령들을 파이프로 연결할 때 사용합니다.|&
는 stdout, stderr 둘 다 파이프로 전달하며 2>&1 |
와 동일한 의미입니다.
< > >>
이외에도 redirection 에 관련된 여러 메타문자들
>>
(append) , >&
, <>
, &>
( >word 2>&1 ) , &>>
( >>word 2>&1 ) , >|
(override noclobber), <<
(here document) , <<-
(no leading tab) , <<<
(here string)
;
한 줄에 여러 개의 명령을 연이어 작성할 때 분리를 위해 사용합니다.;;
, ;&
, ;;&
는 case 문에서 각 pattern)
의 종료를 나타내는데 사용됩니다.
* ? [ ]
패턴매칭에 사용되는 glob 문자도 메타문자에 속합니다.
" '
문장을 quote 할 때 사용합니다.
space 로 분리된 문장을 quote 하면 하나의 인수가 됩니다.
\
escape 할 때 사용되는 문자입니다.
$
매개변수 확장, 산술 확장, 명령 치환 에 사용됩니다.
= +=
대입연산에 사용됩니다. +=
는 bash 3.1 에서 추가된 기능으로 sh 에서는 사용할 수 없습니다.
메타문자가 명령문에 포함되면 escape 해야한다.
&
메타문자가 들어간 파일 이름을 인수로 사용해 오류 발생
# background process 가 실행되면서 오류발생
$ find * -name foo&bar.txt
[1] 16962
bar.txt: command not found
[1]+ Done
# 다음과 같이 수정합니다.
$ find * -name foo\&bar.txt
$ find * -name foo"&"bar.txt
$ find * -name foo'&'bar.txt
$ find * -name "foo&bar.txt"
$ find * -name 'foo&bar.txt'
find 명령은 인수로 shell 메타문자인 ( )
, ;
을 사용하는데 quote 하거나 escape 하지 않으면
정상적으로 실행이 되지 않습니다.
# '( )' 는 shell 메타문자 이므로 명령문에 바로 사용할시 오류발생
$ find * -type f ( -name "*.log" -or -name "*.bak" ) -exec rm -f {} ;
bash: syntax error near unexpected token '('
# '( )' 는 quote 했으나 ';' 메타문자로 인해 오류발생
$ find * -type f '(' -name "*.log" -or -name "*.bak" ')' -exec rm -f {} ;
find: missing argument to '-exec'
# 다음과 같이 shell 메타문자 들을 모두 escape 합니다.
$ find * -type f '(' -name "*.log" -or -name "*.bak" ')' -exec rm -f {} ';'
OK
$ find * -type f \( -name "*.log" -or -name "*.bak" \) -exec rm -f {} \;
OK
메타문자는 shell 에서 특별히 취급하는 문자다.
그러므로 다른 단어와 공백 없이 붙여서 사용할 수도 있다.
# 메타문자 일경우 '( )'
$ (true)&&(true;false);echo $?
$ 1
# 메타문자가 아닐경우 '{ }'
$ {true;}&&{true;false;};echo $?
bash: syntax error near unexpected token `}'
메타문자는 escape 없이 명령라인 중간에 사용할 수 없다.
$ echo () foo ( ) bar ( zoo ) # 첫 번째 () 는 함수 정의에 해당된다.
bash: syntax error near unexpected token 'foo'
$ echo \(\) foo \( \) bar \( zoo \) # escape 해야한다.
() foo ( ) bar ( zoo )
$ echo {} foo { } bar { zoo } # shell 키워드는 OK
{} foo { } bar { zoo }
&&
로 명령들을 연결할 경우
명령들을 &&
연산자로 연결하면 이전 명령이 정상적으로 완료된 것을 보장할 수 있습니다.
또한 중간에 하나라도 오류로 종료될 경우 나머지 명령들은 실행되지 않게 됩니다.
다음 명령을 실행하면 현재 디렉토리에 bash 5.1 버전 실행파일이 생성됩니다.
# 명령 실행이 완료되려면 gcc, make 명령과 libtinfo-dev 패키지가 설치되어 있어야 합니다.
$ cd `mktemp -d -p /dev/shm 2>&1` &&
( curl -L https://ftp.gnu.org/gnu/bash/bash-5.1.tar.gz | tar -xvz &&
cd bash-5.1 && ./configure && make -j `nproc` && strip bash ) &&
mv bash-5.1/bash "$OLDPWD" && rm -rf "$PWD" && cd -
{ } 는 shell keyword
{ }
는 메타문자가 아니고 키워드로 명령 그룹에 사용되며,
이외에도 함수 정의, 매개변수 확장, brace 확장에 사용됩니다.
# 명령 그룹
{ echo 1; echo 2; echo 3 ;} # 명령 위치에서 사용되므로 shell keyword
# 함수 정의
f1() { echo 1 ;}
# 매개변수 확장
$AA, ${AA}, ${AA:-0}, ${AA//Linux/Unix}
# brace 확장
echo file{1..5}
---------------------------
# 다음과 같은 경우는 find 명령의 인수에 해당하는 문자
$ find * -name '*.o' -exec rm -f {} \;
' ! ' shell keyword
!
도 키워드로 쉘에서 두 가지 기능을 가지고 있습니다.
하나는 명령의 위치에서 공백과 함께 사용되면 logical NOT 으로 사용되고,
다른 하나는 프롬프트 상에서 !
와 공백 없이 붙여서 command history 확장에 사용됩니다.
- logical NOT
$ [ 1 = 1 ]; echo $?
0
$ ! [ 1 = 1 ]; echo $? # 명령 위치에서 사용되면 logical NOT 키워드
1
$ if [ 1 = 1 ]; then echo 111; else echo 222; fi
111
$ if ! [ 1 = 1 ]; then echo 111; else echo 222; fi
222
---------------------------------------------
# 다음과 같은 경우는 명령 위치가 아니므로 logical NOT 키워드로 사용된 것이 아니고
# [, test, find, iptables 명령의 인수에 해당하는 문자가 됩니다.
$ [ ! 1 -eq 2 ]
$ test ! -e dir1/foo -o ! -e dir1/bar
$ find dir1 ! -name "*.o"
$ iptables -A WEB_SSH -p tcp ! -s 10.10.10.10 --dport 22 -j DROP
command history 확장
!
는 프롬프트 상에서 history 확장에 사용되므로 주의해야 합니다.
( non-interactive shell 인 스크립트 파일 실행시에는 history 기능이 disable 됩니다 )
$ lsb_release -d
Description: Ubuntu 15.04
$ echo command history : !lsb # 이전 명령 history 확장
command history : lsb_release -d
$ echo "hello!lsb world" # double quotes 에서도 history 확장이 된다!
hellolsb_release -d world
$ echo 'hello!lsb world'
hello!lsb world
대입 메타문자로 += 를 사용할 수 있다.
+=
대입 메타문자는 변수 와 array 에서 모두 사용할 수 있습니다.
( sh 에서는 사용할 수 없습니다. )
### variable ### ### indexed array ### ### associative array ###
$ declare -A CC
$ AA=hello $ BB=(11 22 33) $ CC=([aa]=111 [bb]=222)
$ AA+=" world" $ BB+=(44) $ CC+=([cc]=333)
$ echo "$AA" $ echo ${BB[@]} $ echo ${CC[@]}
hello world 11 22 33 44 222 111 333
sh
에서는 다음과 같이 하면 됩니다.
$ AA=hello $ set 11 22 33
$ AA="$AA world" $ echo "$@"
11 22 33
$ echo "$AA"
hello world $ set "$@" 44
$ echo "$@"
11 22 33 44
Shell 에는 ,
연산자가 없다
기본적으로 명령의 인수나 array 원소를 구분하기 위해 공백을 사용합니다.
f1() { $ ARR=(11 , 22 , 33)
echo arg1 : $1
echo arg2 : $2 $ echo ${AA[0]}
echo arg3 : $3 11
} $ echo ${AA[1]} # ',' 가 두번째
, # 원소가 된다.
$ f1 11,22,33 $ echo ${AA[2]}
arg1 : 11,22,33 22
arg2 : # 하나의 인수로 전달된다.
arg3 :
$ f1 11, 22, 33
arg1 : 11, # '11,' 가 첫번째 인수가 된다.
arg2 : 22,
arg3 : 33
따라서 명령문에서 ,
를 이용해 옵션 인수를 받을 경우 하나의 인수로
전달되기 위해서 공백을 두어서는 안됩니다.
# ',' 를 공백없이 붙여야 trace= 와함께 하나의 인수로 전달된다.
$ strace -e trace=open,close,read,write command ... # OK
# 다음과 같이 공백을 두면 안됩니다.
$ strace -e trace=open, close, read, write command ... # ERROR
$ gcc -shared -Wl,-soname,libmean.so.1 -o libmean.so.1.0.1 ... # OK
$ iptables -p tcp --tcp-flags ACK,FIN,SYN ... # OK
Quiz
C++ 언어에서 매크로 와 템플릿의 확장 결과를 볼 수 있는 함수를 작성하는 것입니다. 인수는 단순히 헤더이름만 줄수도 있고 소스 파일이름을 줄수도 있습니다.
함수 실행을 위해선 clang++, g++, clang-format 명령이 있어야 됩니다.
# 1. 기본적으로 매크로와 템플릿의 확장 결과가 출력됩니다.
# 2. "-m" 옵션을 사용하면 매크로 확장 결과만 출력됩니다.
# 3. "-l" 옵션은 헤더 파일이 참조하는 파일 리스트를 볼 수 있습니다.
# 4. 기타 컴파일러에 전달할 옵션이 있을 경우에는 "--" 이후에 작성하면 됩니다.
$ header-cpp test.cpp $ header-cpp <<\@
template <typename T>
$ cat test.cpp | header-cpp T add(T a, T b) { return a + b; }
int main() {
$ header-cpp -m iostream add(100, 200);
add(1.23, 1.23);
$ header-cpp -m '#include <iostream>' }
@
$ echo '#include <vector>' | header-cpp -m
function header-cpp () {
local opt D='class __________;'
local command='clang++ -xc++ - -Xclang -ast-print -fsyntax-only -std=c++20'
for opt; do
case $opt in
-m) command='g++ -xc++ - -E -P -dD -std=c++20'; break ;;
-l) command='g++ -xc++ - -M -std=c++20'; break ;;
esac
done
( set -o pipefail # 이후 로는 ( ) 명령 그룹들이 파이프에 연결되므로
( # 각각의 subshell 에서 상속받은 $@ 값을 갖게 됩니다.
for arg; do
case $arg in
-m|-l) continue ;;
--) break ;;
esac
if test -f "$arg"; then
cat "$arg"
else
! [[ $arg =~ ^\#include ]] && echo "#include <$arg>" || echo "$arg"
fi
done
if ! test -t 0; then cat; fi
) |
sed -En -e '/^[ \t]*#[ \t]*include/! s/^/\a/; H;' -e '${ g; tR :R s/\a//g; TX;' \
-e 's/^[ \t]*#[ \t]*include.*$/\n'"$D"'\n&\n'"$D"'\n/Mg; :X p }' |
( while [ $# -gt 0 ]; do [ "$1" = "--" ] && { shift; break; } || shift; done; \
$command "$@" ) |
( [ "$opt" = "-m" ] && clang-format -style="{IndentWidth: 4}" | sed /^"$D"$/d ||
sed /^"$D"$/,/^"$D"$/d )
)
}
-----------------------------------------------------------------------
$ cat test.cpp // 템플릿 확장을 통해 숫자 sequence 를 만드는 예제
#include <iostream>
#include <utility>
template<typename T, T... ints>
void print_sequence(std::integer_sequence<T, ints...> seq)
{
std::cout << "The sequence of size " << seq.size() << " : ";
((std::cout << ints << ' '), ...); // fold expression 확장 결과도 볼수있다.
std::cout << '\n';
}
template <typename T, T START, T END, T STEP, T... Indices>
constexpr auto fn_make_sequence(std::integer_sequence<T, Indices...> seq)
{
if constexpr (STEP > 0 && START < END) // STEP 이 양수면 오름차순 생성
{
return fn_make_sequence<T, START + STEP, END, STEP>
(std::integer_sequence<T, Indices..., START>{});
}
else if constexpr (STEP < 0 && START > END) // STEP 이 음수면 내림차순 생성
{
return fn_make_sequence<T, START + STEP, END, STEP>
(std::integer_sequence<T, Indices..., START>{});
}
else
return seq;
}
// decltype 으로 감싸면 실제 함수가 실행되지는 않고 함수 반환 타입 정보만 취하게 됩니다.
template <typename T, T START, T END, T STEP>
using make_sequence = decltype(
fn_make_sequence<T, START, END, STEP>(std::integer_sequence<T>{}));
int main()
{
// seq 의 타입은 std::integer_sequence<int, 0, 2, 4, 6, 8> 가 된다.
auto seq = make_sequence<int, 0, 10, 2>{};
print_sequence(seq);
}
-------------------------------------------------------------
# 다음 명령을 실행하면 test.cpp 파일의 템플릿 확장 결과를 볼 수 있습니다.
$ header-cpp test.cpp
SFINAE
(Substitution Failure Is Not An Error) 에 사용되는 enable_if
$ header-cpp <<\@
template <bool, typename T = void> struct enable_if {};
template <typename T> struct enable_if<true, T> { typedef T type; };
int main()
{
enable_if<true>();
enable_if<true, int>();
enable_if<false>();
enable_if<false, int>();
}
@
template <bool, typename T = void> struct enable_if {
};
template<> struct enable_if<true, void> { // 인자로 전달된값이 true 면
typedef void type; // type 이 존재하게 되어
}; // 템플릿 확장이 성공하지만
template<> struct enable_if<true, int> {
typedef int type;
};
template<> struct enable_if<false, void> { // 인자로 전달된값이 false 면
}; // type 이 존재하지 않게되어
template<> struct enable_if<false, int> { // 템플릿 확장이 실패하게 된다.
}; // 이때 컴파일이 중단되는것은 아니고
template <typename T> struct enable_if<true, T> { // (Is Not An Error) 단지 템플릿
typedef T type; // 확장 set 에 포함되지 않게 됩니다.
};
int main() {
enable_if<true>();
enable_if<true, int>();
enable_if<false>();
enable_if<false, int>();
}
오류는 stderr
로 출력되므로 오류만 보고 싶을 경우에는 > /dev/null
와같이 redirect 하면 됩니다.
C++20 에 도입된 concept 은 Bjarne Stroustrup 가 오래전부터 주장해왔던 기능이라고 하는데요.
concept 을 이용하면 컴파일 타임에 템플릿 확장을 통해 static polymorphism 을 쉽게 구현할 수 있습니다.
$ header-cpp <<\@ > /dev/null
#include <iostream>
template <typename T>
concept has_foo = requires(T t) { // concept has_foo 는 타입 T 에
t.foo(); // foo() 함수가 존재해야 한다.
};
template <has_foo T, has_foo U> // has_foo 를 타입 T, U 를 constrain 하는데 사용.
void foobar(T f1, U f2) {
f1.foo();
f2.foo();
}
struct Bar { void foo() { std::cout << "Bar" << '\n'; } };
struct Zoo { void xxx() { std::cout << "Zoo" << '\n'; } }; // xxx() --> foo() 로 변경
int main() {
foobar( Bar{}, Zoo{} );
return 0;
}
@
<stdin>:18:5: error: no matching function for call to 'foobar'
foobar( Bar{}, Zoo{} );
^~~~~~
<stdin>:9:6: note: candidate template ignored: constraints not satisfied [with T = Bar, U = Zoo]
void foobar(T f1, U f2) {
^
<stdin>:8:22: note: because 'Zoo' does not satisfy 'has_foo' <---- has_foo 위반 오류
template <has_foo T, has_foo U>
^
<stdin>:5:7: note: because 't.foo()' would be invalid: no member named 'foo' in 'Zoo'
t.foo();
^
1 error generated.
다음과 같이 concept 를 이용해 operator <<
를 오버로딩 하면
std::array
, std::vector
, std::tuple
, std::span
,
std::pair
, std::map
, std::set
값들을 모두 출력할 수 있습니다.
#include <iostream>
#include <array>
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <span>
#include <tuple>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <variant>
#include <optional>
#include <memory>
template <template <typename...> class T, typename>
struct is_same_template : std::false_type { };
template <template <typename...> class T, typename... Args>
struct is_same_template<T, T<Args...>> : std::true_type { };
// for std::array, std::span
template <template <typename, auto> class T, typename>
struct is_same_template2 : std::false_type { };
template <template <typename, auto> class T, typename Arg, auto N>
struct is_same_template2<T, T<Arg, N>> : std::true_type { };
template <typename T>
concept array_c = is_same_template2<std::array, std::remove_cvref_t<T>>::value;
template <typename T>
concept span_c = is_same_template2<std::span, std::remove_cvref_t<T>>::value;
template <typename T>
concept vector_c = is_same_template<std::vector, std::remove_cvref_t<T>>::value;
template <typename T>
concept deque_c = is_same_template<std::deque, std::remove_cvref_t<T>>::value;
template <typename T>
concept list_c = is_same_template<std::list, std::remove_cvref_t<T>>::value;
template <typename T>
concept forward_list_c = is_same_template<std::forward_list, std::remove_cvref_t<T>>::value;
template <typename T>
concept pair_c = is_same_template<std::pair, std::remove_cvref_t<T>>::value;
template <typename T>
concept map_c = is_same_template<std::map, std::remove_cvref_t<T>>::value;
template <typename T>
concept unordered_map_c = is_same_template<std::unordered_map, std::remove_cvref_t<T>>::value;
template <typename T>
concept unordered_multimap_c = is_same_template<std::unordered_multimap, std::remove_cvref_t<T>>::value;
template <typename T>
concept multimap_c = is_same_template<std::multimap, std::remove_cvref_t<T>>::value;
template <typename T>
concept set_c = is_same_template<std::set, std::remove_cvref_t<T>>::value;
template <typename T>
concept unordered_set_c = is_same_template<std::unordered_set, std::remove_cvref_t<T>>::value;
template <typename T>
concept unordered_multiset_c = is_same_template<std::unordered_multiset, std::remove_cvref_t<T>>::value;
template <typename T>
concept multiset_c = is_same_template<std::multiset, std::remove_cvref_t<T>>::value;
template <typename T>
concept optional_c = is_same_template<std::optional, std::remove_cvref_t<T>>::value;
template <typename T>
concept unique_ptr_c = is_same_template<std::unique_ptr, std::remove_cvref_t<T>>::value;
template <typename T>
concept shared_ptr_c = is_same_template<std::shared_ptr, std::remove_cvref_t<T>>::value;
template <typename T>
concept weak_ptr_c = is_same_template<std::weak_ptr, std::remove_cvref_t<T>>::value;
template <typename T>
concept tuple_c = is_same_template<std::tuple, std::remove_cvref_t<T>>::value;
template <typename T>
concept variant_c = is_same_template<std::variant, std::remove_cvref_t<T>>::value;
template<typename T> // 전방선언
requires pair_c<T> || map_c<T> || unordered_map_c<T> || unordered_multimap_c<T> || multimap_c<T>
std::ostream& operator << (std::ostream& os, T&& container);
template<typename T> requires tuple_c<T>
std::ostream& operator << (std::ostream& os, T&& container);
template<typename T> requires variant_c<T> || optional_c<T>
std::ostream& operator << (std::ostream& os, T&& container);
template<typename T>
requires unique_ptr_c<T> || shared_ptr_c<T> || weak_ptr_c<T>
std::ostream& operator << (std::ostream& os, T&& container);
template<typename T, auto N>
requires (! std::same_as<char, std::remove_extent_t<std::remove_cv_t<T>>>)
std::ostream& operator << (std::ostream& os, const T(&array)[N]);
template<typename T>
requires array_c<T> || vector_c<T> || deque_c<T> || list_c<T> || forward_list_c<T> ||
span_c<T> || set_c<T> || unordered_set_c<T> || unordered_multiset_c<T> || multiset_c<T>
std::ostream& operator << (std::ostream& os, T&& container)
{
if (container.empty()) // array_c, vector_c, deque_c, list_c ...
os << "[ ]"; // 컨셉트를 만족할 경우 다음 코드를
else { // 이용해 값을 출력합니다.
auto begin = container.begin();
auto end = container.end();
os << '[';
for (auto it = begin; it != end; )
os << *it << ( ++it != end ? ", " : "]" );
}
return os;
}
template<typename T>
requires pair_c<T> || map_c<T> || unordered_map_c<T> || unordered_multimap_c<T> || multimap_c<T>
std::ostream& operator << (std::ostream& os, T&& container)
{
if constexpr (pair_c<T>)
os << '{' << container.first << " : " << container.second << '}';
else {
if (container.empty()) // pair_c, map_c, unordered_map_c ...
os << "{ }"; // 컨셉트를 만족할 경우 다음 코드를
else { // 이용해 값을 출력합니다.
auto begin = container.cbegin();
auto end = container.cend();
os << '{';
for (auto it = begin; it != end; )
os << '{' << it->first << " : " << it->second << '}'
<< ( ++it != end ? ", " : "}" );
}
}
return os;
}
template<typename T> requires tuple_c<T> // tuple 값을 출력
std::ostream& operator << (std::ostream& os, T&& container)
{
std::apply(
[&os](auto&&... elements) {
std::size_t n{}, size = std::tuple_size_v<std::remove_cvref_t<T>>;
os << '[';
((os << elements << (++n != size ? ", " : "")), ...); // fold expression
os << ']';
},
std::forward<T>(container));
return os;
}
template<typename T> // variant, optional 값을 출력
requires variant_c<T> || optional_c<T>
std::ostream& operator << (std::ostream& os, T&& container)
{
if constexpr (variant_c<T>)
std::visit( [&os](auto&& v){ os << v; }, std::forward<T>(container));
else if constexpr (optional_c<T>) {
if (container.has_value())
os << container.value();
}
return os;
}
template<typename T> // smart pointer 값을 출력
requires unique_ptr_c<T> || shared_ptr_c<T> || weak_ptr_c<T>
std::ostream& operator << (std::ostream& os, T&& container)
{
if constexpr(weak_ptr_c<T>) {
if (container.expired())
return os;
else
return os << *container.lock();
} else
return os << *container;
}
template<typename T, auto N> // C array 값을 출력. char 배열은 제외 (기존 방법으로 출력)
requires (! std::same_as<char, std::remove_extent_t<std::remove_cv_t<T>>>)
std::ostream& operator << (std::ostream& os, const T(&array)[N])
{
os << '[';
for (std::size_t i = 0; i < N; )
os << array[i] << (++i == N ? "" : ", ");
return os << ']';
}
int main()
{
using namespace std::string_literals;
std::array array = { 11, 22, 33 };
std::vector vector = { 44, 55, 66 };
std::deque deque = { 77, 88, 99 };
std::list list = { 77, 88, 99 };
std::span span = { array };
std::tuple tuple = { "hello"s, 3.14, array, vector, list };
std::pair pair = { "foo"s, 100 };
auto map = std::map { pair, {"bar"s, 200}, {"zoo"s, 300} };
std::set set = { "foo"s, "bar"s, "zoo"s }; // std::string 이어야 정렬이 된다.
std::cout << "std::array\t" << array << '\n'; // << 연산자로 array, vector, span
std::cout << "std::vector\t" << vector << '\n'; // tuple, pair, map, set 값을 모두
std::cout << "std::deque\t" << deque << '\n'; // 출력할 수 있다.
std::cout << "std::list\t" << list << '\n';
std::cout << "std::span\t" << span << '\n';
std::cout << "std::tuple\t" << tuple << '\n';
std::cout << "std::pair\t" << pair << '\n';
std::cout << "std::map\t" << map << '\n';
std::cout << "std::set\t" << set << '\n';
}
-----------------------------------------------
$ g++ -std=c++20 test.cpp
$ ./a.out
std::array [11, 22, 33]
std::vector [44, 55, 66]
std::deque [77, 88, 99]
std::list [77, 88, 99]
std::span [11, 22, 33]
std::tuple [hello, 3.14, [11, 22, 33], [44, 55, 66], [77, 88, 99]]
std::pair {foo : 100}
std::map {{bar : 200}, {foo : 100}, {zoo : 300}}
std::set [bar, foo, zoo]
C++ 템플릿 메타프로그래밍 강좌
https://youtu.be/tiAVWcjIF6o
https://www.youtube.com/user/siliners/videos
https://www.youtube.com/channel/UCgSmRttyuBvl_ng9QxjX0wQ/videos