{"id":15039352,"url":"https://github.com/sewenew/redis-plus-plus","last_synced_at":"2025-05-14T05:10:05.987Z","repository":{"id":39847117,"uuid":"114471005","full_name":"sewenew/redis-plus-plus","owner":"sewenew","description":"Redis client written in C++","archived":false,"fork":false,"pushed_at":"2025-04-08T15:05:54.000Z","size":1485,"stargazers_count":1751,"open_issues_count":50,"forks_count":357,"subscribers_count":39,"default_branch":"master","last_synced_at":"2025-04-11T00:01:53.410Z","etag":null,"topics":["c-plus-plus","connection-pool","cpp","cpp11","hiredis","redis","redis-client","redis-cluster","redis-cluster-client","redis-sentinels","redlock"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sewenew.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-12-16T15:35:05.000Z","updated_at":"2025-04-09T08:26:22.000Z","dependencies_parsed_at":"2023-02-10T16:00:29.025Z","dependency_job_id":"ff1dc4f7-f4d0-4d61-9f74-cf27335ba83c","html_url":"https://github.com/sewenew/redis-plus-plus","commit_stats":{"total_commits":501,"total_committers":26,"mean_commits":19.26923076923077,"dds":"0.10379241516966065","last_synced_commit":"afa0e3fcf131f91836bb060ead12b9171ed40a64"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sewenew%2Fredis-plus-plus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sewenew%2Fredis-plus-plus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sewenew%2Fredis-plus-plus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sewenew%2Fredis-plus-plus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sewenew","download_url":"https://codeload.github.com/sewenew/redis-plus-plus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076839,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-plus-plus","connection-pool","cpp","cpp11","hiredis","redis","redis-client","redis-cluster","redis-cluster-client","redis-sentinels","redlock"],"created_at":"2024-09-24T20:42:32.612Z","updated_at":"2025-05-14T05:10:05.960Z","avatar_url":"https://github.com/sewenew.png","language":"C++","funding_links":[],"categories":["C++"],"sub_categories":[],"readme":"# redis-plus-plus\n\n[![Build Status](https://github.com/sewenew/redis-plus-plus/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/sewenew/redis-plus-plus/actions/workflows/build-and-test.yml)\n\n[中文交流群](https://github.com/sewenew/redis-plus-plus/blob/master/Chinese.md)\n\nI create a Redis module, named [redis-llm](https://github.com/sewenew/redis-llm), which integrates LLMs (Large Language Models) with Redis. You can [learn redis-plus-plus by asking questions with it](https://github.com/sewenew/redis-llm/tree/main/examples/search-application).\n\n- [Overview](#overview)\n    - [Features](#features)\n- [Installation](#installation)\n    - [Install hiredis](#install-hiredis)\n    - [Install redis-plus-plus](#install-redis-plus-plus)\n    - [Run Tests (Optional)](#run-tests-optional)\n    - [Use redis-plus-plus In Your Project](#use-redis-plus-plus-in-your-project)\n- [Getting Started](#getting-started)\n- [API Reference](#api-reference)\n    - [Connection](#connection)\n    - [Send Command to Redis Server](#send-command-to-redis-server)\n    - [Exception](#exception)\n    - [Generic Command Interface](#generic-command-interface)\n    - [Publish/Subscribe](#publishsubscribe)\n    - [Pipeline](#pipeline)\n    - [Transaction](#transaction)\n    - [Redis Cluster](#redis-cluster)\n    - [Redis Sentinel](#redis-sentinel)\n    - [Redis Stream](#redis-stream)\n    - [Redis Modules](#redis-modules)\n    - [Async Interface](#async-interface)\n    - [Coroutine Interface](#coroutine-interface)\n- [Redis Patterns](#redis-patterns)\n    - [Redlock](#redlock)\n- [Breaking Changes](#breaking-changes)\n- [Author](#author)\n\n## Overview\n\nThis is a C++ client library for Redis. It's based on [hiredis](https://github.com/redis/hiredis), and is compatible with C++11 and later versions. Besides Redis, it also works with other key-value stores supporting RESP (REdis Serialization Protocol), e.g. Valkey, DragonflyDB, KeyDB.\n\n**NOTE**: I'm not a native speaker. So if the documentation is unclear, please feel free to open an issue or pull request. I'll response ASAP.\n\n### Features\n- Most commands for Redis.\n- Connection pool.\n- Redis scripting.\n- Thread safe unless otherwise stated.\n- Redis publish/subscribe.\n- Redis pipeline.\n- Redis transaction.\n- Redis Cluster.\n- Redis Sentinel.\n- STL-like interfaces.\n- Generic command interface.\n- Redis Stream.\n- Redlock.\n- Redis ACL.\n- TLS/SSL support.\n- Sync and Async interface.\n- Coroutine support.\n\n## Installation\n\n### Install hiredis\n\nSince *redis-plus-plus* is based on *hiredis*, you should install *hiredis* first. The minimum version requirement for *hiredis* is **v0.12.1**. However, [the latest stable release](https://github.com/redis/hiredis/releases) of *hiredis* is always recommended.\n\n**NOTE**: You must ensure that there's only 1 version of hiredis is installed. Otherwise, you might get some wired problems. Check the following issues for example: [issue 135](https://github.com/sewenew/redis-plus-plus/issues/135), [issue 140](https://github.com/sewenew/redis-plus-plus/issues/140) and [issue 158](https://github.com/sewenew/redis-plus-plus/issues/158).\n\nNormally, you can install *hiredis* with a C++ package manager, and that's the easiest way to do it, e.g. `sudo apt-get install libhiredis-dev`. However, if you want to install the latest code of hiredis, or a specified version (e.g. async support needs hiredis v1.0.0 or later), you can install it from source.\n\nNote again: DO NOT INSTALL MULTIPLE VERSIONS OF HIREDIS.\n\n```shell\ngit clone https://github.com/redis/hiredis.git\n\ncd hiredis\n\nmake\n\nmake install\n```\n\nBy default, *hiredis* is installed at */usr/local*. If you want to install *hiredis* at non-default location, use the following commands to specify the installation path.\n\n```shell\nmake PREFIX=/non/default/path\n\nmake PREFIX=/non/default/path install\n```\n\n### Install redis-plus-plus\n\n*redis-plus-plus* is built with [CMAKE](https://cmake.org).\n\n```shell\ngit clone https://github.com/sewenew/redis-plus-plus.git\n\ncd redis-plus-plus\n\nmkdir build\n\ncd build\n\ncmake ..\n\nmake\n\nmake install\n\ncd ..\n```\n\nIf *hiredis* is installed at non-default location, you should use `CMAKE_PREFIX_PATH` to specify the installation path of *hiredis*. By default, *redis-plus-plus* is installed at */usr/local*. However, you can use `CMAKE_INSTALL_PREFIX` to install *redis-plus-plus* at non-default location.\n\n```shell\ncmake -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus ..\n```\n\nSince version 1.3.0, by default, *redis-plus-plus* is built with the `-std=c++17` standard. So that we can use the [std::string_view](#stringview) and [std::optional](#optional) features. However, it can also be built with the `-std=c++11` or `-std=c++14` standard, and in that case, we have our own simple implementation of `std::string_view` and `std::optional`. In order to explicitly specify c++ standard, you can use the following cmake flag: `-DREDIS_PLUS_PLUS_CXX_STANDARD=11`.\n\n```shell\ncmake -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus -DREDIS_PLUS_PLUS_CXX_STANDARD=11 ..\n```\n\n**NOTE**: You should build *redis-plus-plus* and your application with the same standard, e.g. if you build *redis-plus-plus* with C++17 standard, you MUST also build your application code with C++17 standard.\n\nWhen compiling *redis-plus-plus*, it also compiles a test program, which might take a while. However, you can disable building test with the following cmake option: `-DREDIS_PLUS_PLUS_BUILD_TEST=OFF`.\n\n```shell\ncmake -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus -DREDIS_PLUS_PLUS_BUILD_TEST=OFF ..\n```\n\nBy default, *redis-plus-plus* builds both a static library and a shared library. If you only want to build one of them, you can disable the other with `-DREDIS_PLUS_PLUS_BUILD_STATIC=OFF` or `-DREDIS_PLUS_PLUS_BUILD_SHARED=OFF`.\n\n*redis-plus-plus* builds static library with `-fPIC` option, i.e. Position Independent Code, by default. However, you can disable it with `-DREDIS_PLUS_PLUS_BUILD_STATIC_WITH_PIC=OFF`.\n\n#### Windows Support\n\nNow *hiredis* has Windows support, and since Visual Studio 2017, Visual Studio has built-in support for CMake. So *redis-plus-plus* also supports Windows platform now. It has been fully tested with Visual Studio 2017 and later on Win 10. I'm not familiar with Visual Studio environment, and the following doc might not be accurate. If you're familiar with the Windows platform, feel free to update this doc on how to install *redis-plus-plus* on Windows.\n\n##### CMake Support On Visual Studio\n\nThe following are some links on how to build CMake project with Visual Studio 2017 or later. If you're not familiar with it, you'd better read these instructions first:\n\n- [CMake support in Visual Studio](https://devblogs.microsoft.com/cppblog/cmake-support-in-visual-studio/)\n- [CMake projects in Visual Studio](https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=vs-2017)\n- [CMakeSettings.json schema reference](https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=vs-2017)\n- [Open a project from a GitHub repo](https://docs.microsoft.com/en-us/visualstudio/get-started/tutorial-open-project-from-repo?view=vs-2019#open-a-project-from-a-github-repo)\n\n**NOTE**: IMHO, Visual Studio 2017's support for CMake project is not very mature, and I recommend you to build *hiredis* and *redis-plus-plus with Visual Studio 2019.\n\n##### Build hiredis\n\nFirst of all, you need to get the latest code of *hiredis* on master branch. Older version might not support Windows platform. *hiredis*' CMakeLists.txt uses `add_compile_definitions` method, which is only supported by cmake 3.12 or later. However, Visual Studio 2017's cmake version is older than that. So if you're using Visual Studio 2017, you need to comment the following line in the CMakeLists.txt file:\n\n```cmake\n#IF(WIN32)\n#    ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN)\n#ENDIF()\n```\n\nYou can use the **Open Folder** feature to open *hiredis* project, and build it with the instructions (links) mentioned above.\n\n##### Build redis-plus-plus\n\nSince *redis-plus-plus* depends on *hiredis*, we need to specify the installation paths of *hiredis* before building it. You can use the **Open Folder** feature to open *redis-plus-plus* project. You need to edit the *CMakeSetting.json* file (automatically generated by Visual Studio) to set *HIREDIS_HEADER*, *HIREDIS_LIB* and *TEST_HIREDIS_LIB* variables to specify the installation path of hiredis headers, installation path of hiredis dynamic library and installation path of hiredis static library. The following is an example of *CMakeSetting.json* file:\n\n```json\n{\n    \"configurations\": [\n      {\n        \"name\": \"x64-Release\",\n        \"generator\": \"Visual Studio 15 2017 Win64\",\n        \"configurationType\": \"Release\",\n        \"buildRoot\": \"${env.LOCALAPPDATA}\\\\CMakeBuild\\\\${workspaceHash}\\\\build\\\\${name}\",\n        \"cmakeCommandArgs\": \"\",\n        \"buildCommandArgs\": \"-m -v:minimal\",\n        \"variables\": [\n          {\n            \"name\": \"HIREDIS_HEADER\",\n            \"value\": \"installation path of hiredis header files\",\n            \"type\": \"PATH\"\n          },\n          {\n            \"name\": \"HIREDIS_LIB\",\n            \"value\": \"installation path of dynamic library of hiredis\",\n            \"type\": \"FILEPATH\"\n          },\n          {\n            \"name\": \"TEST_HIREDIS_LIB\",\n            \"value\": \"installation path of static library of hiredis\",\n            \"type\": \"FILEPATH\"\n          }\n        ]\n      }\n    ]\n}\n```\n\nThen you can build it the instructions (links) mentioned above. If you're building with Visual Studio 2017 in debug mode, you might get [/bigobj error](https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file?view=vs-2017) when building the test. In this case, you can disable building test by setting `-DREDIS_PLUS_PLUS_BUILD_TEST=OFF` or build it in Release mode.\n\n**NOTE**:\n\n- Since 1.3.0, *redis-plus-plus* is built with C++17 by default, and you should also set your application code to be built with C++17. If you still want to build the *redis-plus-plus* with C++11, you can set the `REDIS_PLUS_PLUS_CXX_STANDARD` cmake option to 11.\n- TLS/SSL support has not been tested on Windows yet.\n\n##### Build with Visual Studio\n\nIf you want to build the project with Visual Studio and have questions about it, please follow the steps below. The following is tested on Visual Studio 2022 Community. \n\n```powershell\n# download two projects into this folder\nmkdir redis++\n\ncd redis++\n# make sure you create a hiredis first to work as a library\nmkdir hiredis-lib\ncd hiredis-lib\nmkdir lib\n\ngit clone https://github.com/redis/hiredis.git\ncd hiredis\n```\n\nSo far it should be fine with each step. Then open `CMakeLists.txt` file. Modify the following line and comment it out\n```txt\n...\n# SET(CMAKE_DEBUG_POSTFIX d)\n...\n```\n\nThen go back to hiredis project folder\n```powershell\nmkdir build\ncd build\n# convert project into visual studio 2022, if necessary choose you version e.g 19 2019 etc.\ncmake -G \"Visual Studio 17 2022\" ..\n./hiredis.sln\n```\n\nSet `hiredis` as Startup Project then click `Build Solution` in Debug Mode\n\nAfter successfull build, copy all the files under `Debug` into `hiredis-lib/lib` folder\n\nHere the work for hiredis should be finished.\n\nThen go back to `redis++` folder. Open Terminal here\n\n```powershell\ngit clone https://github.com/sewenew/redis-plus-plus.git\ncd redis-plus-plus\nmkdir build\ncd build\n```\n\nNow you should always have openssl on your PC, otherwise can use chocolatey to install it. For Visual Studio 2022 please install pthread separately using `vpckg`, following this [link](https://github.com/microsoft/vcpkg)\n\nAfter all preparation. If you want to convert all projects then \n```powershell\ncmake -DCMAKE_PREFIX_PATH=\"$(ABSOLUTE_PATH)\\hiredis-lib\" -G \"Visual Studio 17 2022\" ..\ncd build\n./redis++.sln\n```\nset `redis++_static` as Startup Project then click `Build Solution`\n\nSo far build has been successfully finished!\n\n##### The Order of Header Files\n\nOn Windows platform, if your application code also needs to include *windows.h*. You must ensure that *sw/redis++/redis++.h* is included before *windows.h*. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/194) for detail.\n\n#### Building a redis-plus-plus Debian Package (Optional)\n\nBasic support for building a GNU/Debian package is supplied with the use of cmake.\nThe following example shows how to build the Debian package:\n\n```shell\nmkdir build; cd build\ncmake ..\ncpack -G DEB\n```\n\nThe install prefix may be modified as follows:\n\n```shell\nmkdir build; cd build\ncmake -DCMAKE_INSTALL_PREFIX=/usr ..\ncpack -G DEB\n```\n\n### Run Tests (Optional)\n\n*redis-plus-plus* has been fully tested with the following compilers:\n\n```shell\ngcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)\ngcc version 5.5.0 20171010 (Ubuntu 5.5.0-12ubuntu1)\ngcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~18.04)\ngcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)\ngcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04.1)\ngcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)\ngcc version 10.2.1 20210110 (Debian 10.2.1-6)\nclang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)\nclang version 4.0.1-10 (tags/RELEASE_401/final)\nclang version 5.0.1-4 (tags/RELEASE_501/final)\nclang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)\nclang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)\nclang version 8.0.1-3build1 (tags/RELEASE_801/final)\nApple clang version 11.0.0 (clang-1100.0.33.12)\nVisual Studio 2017 (Win 10)\nVisual Studio 2019 (Win 10)\n```\n\nIf you build *redis-plus-plus* with `-DREDIS_PLUS_PLUS_BUILD_TEST=ON` (the default behavior, and you can disable building test with `-DREDIS_PLUS_PLUS_BUILD_TEST=OFF`), you'll get a test program in *build/test* directory: *build/test/test_redis++*.\n\nIn order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version. Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future.\n\n**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, i.e. Redis 2.0 and above.\n\n**NEVER** run the test program in production envronment, since the keys, which the test program reads or writes, might conflict with your application.\n\nIn order to run tests with both Redis and Redis Cluster, you can run the test program with the following command:\n\n```shell\n./build/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port\n```\n\n- *host* and *port* are the host and port number of the Redis instance.\n- *cluster_node* and *cluster_port* are the host and port number of Redis Cluster. You only need to set the host and port number of a single node in the cluster, *redis-plus-plus* will find other nodes automatically.\n- *auth* is the password of the Redis instance and Redis Cluster. The Redis instance and Redis Cluster must be configured with the same password. If there's no password configured, don't set this option.\n\nIf you only want to run tests with Redis, you only need to specify *host*, *port* and *auth* options:\n\n```shell\n./build/test/test_redis++ -h host -p port -a auth\n```\n\nSimilarly, if you only want to run tests with Redis Cluster, just specify *cluster_node*, *cluster_port* and *auth* options:\n\n```shell\n./build/test/test_redis++ -a auth -n cluster_node -c cluster_port\n```\n\nBy default, the test program will not test running *redis-plus-plus* in multi-threads environment. If you want to do multi-threads test, which might cost a long time, you can specify the *-m* option:\n\n```shell\n./build/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -m\n```\n\nIf all tests have been passed, the test program will print the following message:\n\n```shell\nPass all tests\n```\n\nOtherwise, it prints the error message.\n\n#### Performance\n\n*redis-plus-plus* runs as fast as *hiredis*, since it's a wrapper of *hiredis*. You can run *test_redis++* in benchmark mode to check the performance in your environment.\n\n```shell\n./build/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -b -t thread_num -s connection_pool_size -r request_num -k key_len -v val_len\n```\n\n- *-b* option turns the test program into benchmark mode.\n- *thread_num* specifies the number of worker threads. `10` by default.\n- *connection_pool_size* specifies the size of the connection pool. `5` by default.\n- *request_num* specifies the total number of requests sent to server for each test. `100000` by default.\n- *key_len* specifies the length of the key for each operation. `10` by default.\n- *val_len* specifies the length of the value. `10` by default.\n\nThe bechmark will generate `100` random binary keys for testing, and the size of these keys is specified by *key_len*. When the benchmark runs, it will read/write with these keys. So **NEVER** run the test program in your production environment, otherwise, it might inaccidently delete your data.\n\n### Use redis-plus-plus In Your Project\n\nAfter compiling the code, you'll get both shared library and static library. Since *redis-plus-plus* depends on *hiredis*, you need to link both libraries to your Application. Also don't forget to specify the c++ standard, `-std=c++17`, `-std=c++14` or `-std=c++11`, as well as the thread-related option.\n\n#### Use Static Libraries\n\nTake gcc as an example.\n\n```shell\ng++ -std=c++17 -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread\n```\n\nIf *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` option to specify the header path.\n\n```shell\ng++ -std=c++17 -I/non-default/install/include/path -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread\n```\n\n#### Use Shared Libraries\n\n```shell\ng++ -std=c++17 -o app app.cpp -lredis++ -lhiredis -pthread\n```\n\nIf *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` and `-L` options to specify the header and library paths.\n\n```shell\ng++ -std=c++17 -I/non-default/install/include/path -L/non-default/install/lib/path -o app app.cpp -lredis++ -lhiredis -pthread\n```\n\nWhen linking with shared libraries, and running your application, you might get the following error message:\n\n```shell\nerror while loading shared libraries: xxx: cannot open shared object file: No such file or directory.\n```\n\nThat's because the linker cannot find the shared libraries. In order to solve the problem, you can add the path where you installed *hiredis* and *redis-plus-plus* libraries, to `LD_LIBRARY_PATH` environment variable. For example:\n\n```shell\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib\n```\n\nCheck [this StackOverflow question](https://stackoverflow.com/questions/480764) for details on how to solve the problem.\n\n#### Build With Cmake\n\nIf you're using cmake to build your application, you need to add *hiredis* and *redis-plus-plus* dependencies in your *CMakeLists.txt*:\n\n```CMake\n# \u003c---------- set c++ standard -------------\u003e\n# NOTE: you must build redis-plus-plus and your application code with the same standard.\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# \u003c------------ add hiredis dependency ---------------\u003e\nfind_path(HIREDIS_HEADER hiredis)\ntarget_include_directories(target PUBLIC ${HIREDIS_HEADER})\n\nfind_library(HIREDIS_LIB hiredis)\ntarget_link_libraries(target ${HIREDIS_LIB})\n\n# \u003c------------ add redis-plus-plus dependency --------------\u003e\n# NOTE: this should be *sw* NOT *redis++*\nfind_path(REDIS_PLUS_PLUS_HEADER sw)\ntarget_include_directories(target PUBLIC ${REDIS_PLUS_PLUS_HEADER})\n\nfind_library(REDIS_PLUS_PLUS_LIB redis++)\ntarget_link_libraries(target ${REDIS_PLUS_PLUS_LIB})\n```\n\nSee [this issue](https://github.com/sewenew/redis-plus-plus/issues/5) for a complete example of *CMakeLists.txt*.\n\nAlso, if you installed *hiredis* and *redis-plus-plus* at non-default location, you need to run cmake with `CMAKE_PREFIX_PATH` option to specify the installation path of these two libraries.\n\n```\ncmake -DCMAKE_PREFIX_PATH=/installation/path/to/the/two/libs ..\n```\n\n## Getting Started\n\n```C++\n#include \u003csw/redis++/redis++.h\u003e\n\nusing namespace sw::redis;\n\ntry {\n    // Create an Redis object, which is movable but NOT copyable.\n    auto redis = Redis(\"tcp://127.0.0.1:6379\");\n\n    // ***** STRING commands *****\n\n    redis.set(\"key\", \"val\");\n    auto val = redis.get(\"key\");    // val is of type OptionalString. See 'API Reference' section for details.\n    if (val) {\n        // Dereference val to get the returned value of std::string type.\n        std::cout \u003c\u003c *val \u003c\u003c std::endl;\n    }   // else key doesn't exist.\n\n    // ***** LIST commands *****\n\n    // std::vector\u003cstd::string\u003e to Redis LIST.\n    std::vector\u003cstd::string\u003e vec = {\"a\", \"b\", \"c\"};\n    redis.rpush(\"list\", vec.begin(), vec.end());\n\n    // std::initializer_list to Redis LIST.\n    redis.rpush(\"list\", {\"a\", \"b\", \"c\"});\n\n    // Redis LIST to std::vector\u003cstd::string\u003e.\n    vec.clear();\n    redis.lrange(\"list\", 0, -1, std::back_inserter(vec));\n\n    // ***** HASH commands *****\n\n    redis.hset(\"hash\", \"field\", \"val\");\n\n    // Another way to do the same job.\n    redis.hset(\"hash\", std::make_pair(\"field\", \"val\"));\n\n    // std::unordered_map\u003cstd::string, std::string\u003e to Redis HASH.\n    std::unordered_map\u003cstd::string, std::string\u003e m = {\n        {\"field1\", \"val1\"},\n        {\"field2\", \"val2\"}\n    };\n    redis.hmset(\"hash\", m.begin(), m.end());\n\n    // Redis HASH to std::unordered_map\u003cstd::string, std::string\u003e.\n    m.clear();\n    redis.hgetall(\"hash\", std::inserter(m, m.begin()));\n\n    // Get value only.\n    // NOTE: since field might NOT exist, so we need to parse it to OptionalString.\n    std::vector\u003cOptionalString\u003e vals;\n    redis.hmget(\"hash\", {\"field1\", \"field2\"}, std::back_inserter(vals));\n\n    // ***** SET commands *****\n\n    redis.sadd(\"set\", \"m1\");\n\n    // std::unordered_set\u003cstd::string\u003e to Redis SET.\n    std::unordered_set\u003cstd::string\u003e set = {\"m2\", \"m3\"};\n    redis.sadd(\"set\", set.begin(), set.end());\n\n    // std::initializer_list to Redis SET.\n    redis.sadd(\"set\", {\"m2\", \"m3\"});\n\n    // Redis SET to std::unordered_set\u003cstd::string\u003e.\n    set.clear();\n    redis.smembers(\"set\", std::inserter(set, set.begin()));\n\n    if (redis.sismember(\"set\", \"m1\")) {\n        std::cout \u003c\u003c \"m1 exists\" \u003c\u003c std::endl;\n    }   // else NOT exist.\n\n    // ***** SORTED SET commands *****\n\n    redis.zadd(\"sorted_set\", \"m1\", 1.3);\n\n    // std::unordered_map\u003cstd::string, double\u003e to Redis SORTED SET.\n    std::unordered_map\u003cstd::string, double\u003e scores = {\n        {\"m2\", 2.3},\n        {\"m3\", 4.5}\n    };\n    redis.zadd(\"sorted_set\", scores.begin(), scores.end());\n\n    // Redis SORTED SET to std::vector\u003cstd::pair\u003cstd::string, double\u003e\u003e.\n    // NOTE: The return results of zrangebyscore are ordered, if you save the results\n    // in to `std::unordered_map\u003cstd::string, double\u003e`, you'll lose the order.\n    std::vector\u003cstd::pair\u003cstd::string, double\u003e\u003e zset_result;\n    redis.zrangebyscore(\"sorted_set\",\n            UnboundedInterval\u003cdouble\u003e{},            // (-inf, +inf)\n            std::back_inserter(zset_result));\n\n    // Only get member names:\n    // pass an inserter of std::vector\u003cstd::string\u003e type as output parameter.\n    std::vector\u003cstd::string\u003e without_score;\n    redis.zrangebyscore(\"sorted_set\",\n            BoundedInterval\u003cdouble\u003e(1.5, 3.4, BoundType::CLOSED),   // [1.5, 3.4]\n            std::back_inserter(without_score));\n\n    // Get both member names and scores:\n    // pass an back_inserter of std::vector\u003cstd::pair\u003cstd::string, double\u003e\u003e as output parameter.\n    std::vector\u003cstd::pair\u003cstd::string, double\u003e\u003e with_score;\n    redis.zrangebyscore(\"sorted_set\",\n            BoundedInterval\u003cdouble\u003e(1.5, 3.4, BoundType::LEFT_OPEN),    // (1.5, 3.4]\n            std::back_inserter(with_score));\n\n    // ***** SCRIPTING commands *****\n\n    // Script returns a single element.\n    auto num = redis.eval\u003clong long\u003e(\"return 1\", {}, {});\n\n    // Script returns an array of elements.\n    std::vector\u003cstd::string\u003e nums;\n    redis.eval(\"return {ARGV[1], ARGV[2]}\", {}, {\"1\", \"2\"}, std::back_inserter(nums));\n\n    // mset with TTL\n    auto mset_with_ttl_script = R\"(\n        local len = #KEYS\n        if (len == 0 or len + 1 ~= #ARGV) then return 0 end\n        local ttl = tonumber(ARGV[len + 1])\n        if (not ttl or ttl \u003c= 0) then return 0 end\n        for i = 1, len do redis.call(\"SET\", KEYS[i], ARGV[i], \"EX\", ttl) end\n        return 1\n    )\";\n\n    // Set multiple key-value pairs with TTL of 60 seconds.\n    auto keys = {\"key1\", \"key2\", \"key3\"};\n    std::vector\u003cstd::string\u003e args = {\"val1\", \"val2\", \"val3\", \"60\"};\n    redis.eval\u003clong long\u003e(mset_with_ttl_script, keys.begin(), keys.end(), args.begin(), args.end());\n\n    // ***** Pipeline *****\n\n    // Create a pipeline.\n    auto pipe = redis.pipeline();\n\n    // Send mulitple commands and get all replies.\n    auto pipe_replies = pipe.set(\"key\", \"value\")\n                            .get(\"key\")\n                            .rename(\"key\", \"new-key\")\n                            .rpush(\"list\", {\"a\", \"b\", \"c\"})\n                            .lrange(\"list\", 0, -1)\n                            .exec();\n\n    // Parse reply with reply type and index.\n    auto set_cmd_result = pipe_replies.get\u003cbool\u003e(0);\n\n    auto get_cmd_result = pipe_replies.get\u003cOptionalString\u003e(1);\n\n    // rename command result\n    pipe_replies.get\u003cvoid\u003e(2);\n\n    auto rpush_cmd_result = pipe_replies.get\u003clong long\u003e(3);\n\n    std::vector\u003cstd::string\u003e lrange_cmd_result;\n    pipe_replies.get(4, back_inserter(lrange_cmd_result));\n\n    // ***** Transaction *****\n\n    // Create a transaction.\n    auto tx = redis.transaction();\n\n    // Run multiple commands in a transaction, and get all replies.\n    auto tx_replies = tx.incr(\"num0\")\n                        .incr(\"num1\")\n                        .mget({\"num0\", \"num1\"})\n                        .exec();\n\n    // Parse reply with reply type and index.\n    auto incr_result0 = tx_replies.get\u003clong long\u003e(0);\n\n    auto incr_result1 = tx_replies.get\u003clong long\u003e(1);\n\n    std::vector\u003cOptionalString\u003e mget_cmd_result;\n    tx_replies.get(2, back_inserter(mget_cmd_result));\n\n    // ***** Generic Command Interface *****\n\n    // There's no *Redis::client_getname* interface.\n    // But you can use *Redis::command* to get the client name.\n    val = redis.command\u003cOptionalString\u003e(\"client\", \"getname\");\n    if (val) {\n        std::cout \u003c\u003c *val \u003c\u003c std::endl;\n    }\n\n    // Same as above.\n    auto getname_cmd_str = {\"client\", \"getname\"};\n    val = redis.command\u003cOptionalString\u003e(getname_cmd_str.begin(), getname_cmd_str.end());\n\n    // There's no *Redis::sort* interface.\n    // But you can use *Redis::command* to send sort the list.\n    std::vector\u003cstd::string\u003e sorted_list;\n    redis.command(\"sort\", \"list\", \"ALPHA\", std::back_inserter(sorted_list));\n\n    // Another *Redis::command* to do the same work.\n    auto sort_cmd_str = {\"sort\", \"list\", \"ALPHA\"};\n    redis.command(sort_cmd_str.begin(), sort_cmd_str.end(), std::back_inserter(sorted_list));\n\n    // ***** Redis Cluster *****\n\n    // Create a RedisCluster object, which is movable but NOT copyable.\n    auto redis_cluster = RedisCluster(\"tcp://127.0.0.1:7000\");\n\n    // RedisCluster has similar interfaces as Redis.\n    redis_cluster.set(\"key\", \"value\");\n    val = redis_cluster.get(\"key\");\n    if (val) {\n        std::cout \u003c\u003c *val \u003c\u003c std::endl;\n    }   // else key doesn't exist.\n\n    // Keys with hash-tag.\n    redis_cluster.set(\"key{tag}1\", \"val1\");\n    redis_cluster.set(\"key{tag}2\", \"val2\");\n    redis_cluster.set(\"key{tag}3\", \"val3\");\n\n    std::vector\u003cOptionalString\u003e hash_tag_res;\n    redis_cluster.mget({\"key{tag}1\", \"key{tag}2\", \"key{tag}3\"},\n            std::back_inserter(hash_tag_res));\n\n} catch (const Error \u0026e) {\n    // Error handling.\n}\n```\n\n## API Reference\n\nYou can also see [redis.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/redis.h) for doxygen style documentation.\n\n### Connection\n\n`Redis` class maintains a connection pool to Redis server. If the connection is broken, `Redis` reconnects to Redis server automatically.\n\nYou can initialize a `Redis` instance with `ConnectionOptions` and `ConnectionPoolOptions`. `ConnectionOptions` specifies options for connection to Redis server, and `ConnectionPoolOptions` specifies options for conneciton pool. `ConnectionPoolOptions` is optional. If not specified, `Redis` maintains a single connection to Redis server.\n\n```C++\nConnectionOptions connection_options;\nconnection_options.host = \"127.0.0.1\";  // Required.\nconnection_options.port = 6666; // Optional. The default port is 6379.\nconnection_options.password = \"auth\";   // Optional. No password by default.\nconnection_options.db = 1;  // Optional. Use the 0th database by default.\n\n// Optional. Timeout before we successfully send request to or receive response from redis.\n// By default, the timeout is 0ms, i.e. never timeout and block until we send or receive successfuly.\n// NOTE: if any command is timed out, we throw a TimeoutError exception.\nconnection_options.socket_timeout = std::chrono::milliseconds(200);\n\n// Connect to Redis server with a single connection.\nRedis redis1(connection_options);\n\nConnectionPoolOptions pool_options;\npool_options.size = 3;  // Pool size, i.e. max number of connections.\n\n// Optional. Max time to wait for a connection. 0ms by default, which means wait forever.\n// Say, the pool size is 3, while 4 threds try to fetch the connection, one of them will be blocked.\npool_options.wait_timeout = std::chrono::milliseconds(100);\n\n// Optional. Max lifetime of a connection. 0ms by default, which means never expire the connection.\n// If the connection has been created for a long time, i.e. more than `connection_lifetime`,\n// it will be expired and reconnected.\npool_options.connection_lifetime = std::chrono::minutes(10);\n\n// Connect to Redis server with a connection pool.\nRedis redis2(connection_options, pool_options);\n```\n\n**NOTE**: if you set `ConnectionOptions::socket_timeout`, and try to call blocking commands, e.g. `Redis::brpop`, `Redis::blpop`, `Redis::bzpopmax`, `Redis::bzpopmin`, you must ensure that `ConnectionOptions::socket_timeout` is larger than the timeout specified with these blocking commands. Otherwise, you might get `TimeoutError`, and lose messages.\n\nSee [ConnectionOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection.h#L40) and [ConnectionPoolOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection_pool.h#L30) for more options. Also see [issue 80](https://github.com/sewenew/redis-plus-plus/issues/80) for discussion on connection pool.\n\n**NOTE**: `Redis` class is movable but NOT copyable.\n\n```C++\n// auto redis3 = redis1;    // this won't compile.\n\n// But it's movable.\nauto redis3 = std::move(redis1);\n```\n\n*redis-plus-plus* also supports connecting to Redis server with Unix Domain Socket.\n\n```C++\nConnectionOptions options;\noptions.type = ConnectionType::UNIX;\noptions.path = \"/path/to/socket\";\nRedis redis(options);\n```\n\nYou can also connect to Redis server with a URI:\n\n```\ntcp://[[username:]password@]host[:port][/db]\n\nredis://[[username:]password@]host[:port][/db]\n\nunix://[[username:]password@]path-to-unix-domain-socket[/db]\n```\n\nThe *scheme* and *host* parts are required, and others are optional. If you're connecting to Redis with Unix Domain Socket, you should use the *unix* scheme, otherwise, you should use *tcp* or *redis* scheme. The following is a list of default values for those optional parts:\n\n- username: *default*\n- password: empty string, i.e. no password\n- port: 6379\n- db: 0\n\n**NOTE**: If your password or username contains '@', or your username contains ':', you cannot construct `Redis` object with URI. Because *redis-plus-plus* will incorrectly parse the URI. In this case, you need to use `ConnectionOptions` to construct `Redis` object.\n\n**NOTE**: [Redis 6.0 supports ACL](https://redis.io/topics/acl), and you can specify a username for the connection. However, before Redis 6.0, you cannot do that.\n\nAlso, the following connection options and connection pool options can be specified with the query string of URI, e.g. *tcp://127.0.0.1?keep_alive=true\u0026socket_timeout=100ms\u0026connect_timeout=100ms*:\n\n| Option | Parameter | Default |\n| :---------: | :---------: | :---------: |\n| `ConnectionOptions::user` | *user* | *default* |\n| `ConnectionOptions::password` | *password* | empty string, i.e. no password |\n| `ConnectionOptions::db` | *db* | 0 |\n| `ConnectionOptions::keep_alive` | *keep_alive* | false |\n| `ConnectionOptions::connect_timeout` | *connect_timeout* | 0ms |\n| `ConnectionOptions::socket_timeout` | *socket_timeout* | 0ms |\n| `ConnectionOptions::resp` | *resp* | 2 |\n| `ConnectionPoolOptions::size` | *pool_size* | 1 |\n| `ConnectionPoolOptions::wait_timeout` | *pool_wait_timeout* | 0ms |\n| `ConnectionPoolOptions::connection_lifetime` | *pool_connection_lifetime* | 0ms |\n| `ConnectionPoolOptions::connection_idle_time` | *pool_connection_idle_time* | 0ms |\n\n**NOTE**:\n\n- Options specified in query string are case-sensitive, i.e. all key-value pairs must be in lowercase.\n- Options specified in query string, e.g. *user*, *password*, *db*, overwrites the one specified in URI. For example, *redis://127.0.0.1/1?db=3* means that all reads/writes run on the 3rd database, instead of the 1st one.\n\n```C++\n// Single connection to the given host and port.\nRedis redis1(\"tcp://127.0.0.1:6666\");\n\n// Use default port, i.e. 6379.\nRedis redis2(\"tcp://127.0.0.1\");\n\n// Connect to Redis with password, and default port.\nRedis redis3(\"tcp://pass@127.0.0.1\");\n\n// Connect to Redis and select the 2nd (db number starts from 0) database.\nRedis redis4(\"tcp://127.0.0.1:6379/2\");\n\n// Set keep_alive option to true with query string.\nRedis redis5(\"tcp://127.0.0.1:6379/2?keep_alive=true\");\n\n// Set socket_timeout to 50 milliseconds, and connect_timeout to 1 second with query string.\nRedis redis6(\"tcp://127.0.0.1?socket_timeout=50ms\u0026connect_timeout=1s\");\n\n// Connect to Unix Domain Socket.\nRedis redis7(\"unix://path/to/socket\");\n```\n\n#### RESP3\n\nSince Redis 6.0, it supports a new version of Redis protocol, i.e. RESP3. In order to use this new protocol, you need to set `ConnectionOptions::resp` to be 3.\n\n```\nConnectionOptions opts;\nopts.resp = 3;\n// Set other options...\n```\n\nBy default, `ConnectionOptions::resp` is 2, i.e. use RESP version 2. So far, only version 2 and 3 are supported, and the behavior is undefined, if you set `ConnectionOptions::resp` to other numbers.\n\n**NOTE**: In order to use this new protocol, you need to install the latest hiredis (even hiredis-v1.0.2 has bugs on RESP3 support).\n\n#### Lazily Create Connection\n\nConnections in the pool are lazily created. When the connection pool is initialized, i.e. the constructor of `Redis`, `Redis` does NOT connect to the server. Instead, it connects to the server only when you try to send command. In this way, we can avoid unnecessary connections. So if the pool size is 5, but the number of max concurrent connections is 3, there will be only 3 connections in the pool.\n\n#### Connection Failure\n\nYou don't need to check whether `Redis` object connects to server successfully. If `Redis` fails to create a connection to Redis server, or the connection is broken at some time, it throws an exception of type `Error` when you try to send command with `Redis`. Even when you get an exception, i.e. the connection is broken, you don't need to create a new `Redis` object. You can reuse the `Redis` object to send commands, and the `Redis` object will try to reconnect to server automatically. If it reconnects successfully, it sends command to server. Otherwise, it throws an exception again.\n\nSee the [Exception section](#exception) for details on exceptions.\n\n#### Reuse Redis object As Much As Possible\n\nIt's NOT cheap to create a `Redis` object, since it will create new connections to Redis server. So you'd better reuse `Redis` object as much as possible. Also, it's safe to call `Redis`' member functions in multi-thread environment, and you can share `Redis` object in multiple threads.\n\n```C++\n// This is GOOD practice.\nauto redis = Redis(\"tcp://127.0.0.1\");\nfor (auto idx = 0; idx \u003c 100; ++idx) {\n    // Reuse the Redis object in the loop.\n    redis.set(\"key\", \"val\");\n}\n\n// This is VERY BAD! It's very inefficient.\n// NEVER DO IT!!!\nfor (auto idx = 0; idx \u003c 100; ++idx) {\n    // Create a new Redis object for each iteration.\n    auto redis = Redis(\"tcp://127.0.0.1\");\n    redis.set(\"key\", \"val\");\n}\n```\n\n#### TLS/SSL Support\n\n*redis-plus-plus* also has TLS support. However, in order to use this feature, you need to enable it when building *hiredis* and *redis-plus-plus*.\n\n**NOTE**: So far, TLS feature has not been tested on Windows platform. I'll fix it in the future.\n\n##### Enable TLS/SSL support\n\nWhen building *hiredis* with TLS support, you need to download *hiredis* of version *v1.0.0* or latter, and specify `USE_SSL=1` flag:\n\n```shell\nmake PREFIX=/non/default/path USE_SSL=1\n\nmake PREFIX=/non/default/path USE_SSL=1 install\n```\n\nThen you can build *redis-plus-plus* to enable TLS support by specifying the `-DREDIS_PLUS_PLUS_USE_TLS=ON` option:\n\n```shell\ncmake -DREDIS_PLUS_PLUS_USE_TLS=ON ..\n```\n\n##### Connection Options\n\nIn order to connect to Redis with TLS support, you need to specify the following connection options:\n\n```c++\nConnectionOptions opts;\nopts.host = \"127.0.0.1\";\nopts.port = 6379;\n\nopts.tls.enabled = true;    // Required. `false` by default.\nopts.tls.cert = \"/path/to/client/certificate\";  // Optional\nopts.tls.key = \"/path/to/private/key/file\"; // Optional\nopts.tls.cacert = \"/path/to/CA/certificate/file\";   // You can also set `opts.tls.cacertdir` instead.\nopts.tls.sni = \"server-name-indication\";    // Optional\n```\n\nAlthough `tls.cert` and `tls.key` are optional, if you specify one of them, you must also specify the other. Instead of specifying `tls.cacert`, you can also specify `tls.cacertdir` to the directory where certificates are stored.\n\nThese options are the same as `redis-cli`'s TLS related command line arguments, so you can also run `redis-cli --help` to get the detailed explanation of these options.\n\nThen you can use this `ConnectionOptions` to create a `Redis` object to connect to Redis server with TLS support.\n\n**NOTE**: When building your application code, you also need to link it with `libhiredis.a`, `libhiredis_ssl.a`, `libredis++.a` (or the corresponding shared libraries), `-lssl` and `-lcrypto`.\n\n##### Automatically Initialize OpenSSL Library\n\nBy default, *redis-plus-plus* automatically initializes OpenSSL library, i.e. calls `SSL_library_init` and initializes locks if needed. However, your application code might already initialize OpenSSL library. In this case, you can call `tls::disable_auto_init()` to disable the initialization. You should call this function only once and call it before any other *redis-plus-plus* operation. Otherwise, the behavior is undefined.\n\n##### Skip Certificate Verification\n\nSince hiredis v1.1.0, it supports [skipping certificate verification](https://github.com/redis/hiredis/pull/1085). If you want to use this feature with *redis-plus-plus*, you can check [this issue](https://github.com/sewenew/redis-plus-plus/issues/183) for an example.\n\n### Send Command to Redis Server\n\nYou can send [Redis commands](https://redis.io/commands) through `Redis` object. `Redis` has one or more (overloaded) methods for each Redis command. The method has the same (lowercased) name as the corresponding command. For example, we have 3 overload methods for the `DEL key [key ...]` command:\n\n```C++\n// Delete a single key.\nlong long Redis::del(const StringView \u0026key);\n\n// Delete a batch of keys: [first, last).\ntemplate \u003ctypename Input\u003e\nlong long Redis::del(Input first, Input last);\n\n// Delete keys in the initializer_list.\ntemplate \u003ctypename T\u003e\nlong long Redis::del(std::initializer_list\u003cT\u003e il);\n```\n\nWith input parameters, these methods build a Redis command based on [Redis protocol](https://redis.io/topics/protocol), and send the command to Redis server. Then synchronously receive the reply, parse it, and return to the caller.\n\nLet's take a closer look at these methods' parameters and return values.\n\n#### Parameter Type\n\nMost of these methods have the same parameters as the corresponding commands. The following is a list of parameter types:\n\n| Parameter Type | Explaination | Example | Note |\n| :------------: | ------------ | ------- | ---- |\n| **StringView** | Parameters of string type. Normally used for key, value, member name, field name and so on | ***bool Redis::hset(const StringView \u0026key, const StringView \u0026field, const StringView \u0026val)*** | See the [StringView section](#stringview) for details on `StringView` |\n| **long long** | Parameters of integer type. Normally used for index (e.g. list commands) or integer | ***void ltrim(const StringView \u0026key, long long start, long long stop)*** \u003cbr\u003e ***long long decrby(const StringView \u0026key, long long decrement)*** | |\n| **double** | Parameters of floating-point type. Normally used for score (e.g. sorted set commands) or number of floating-point type | ***double incrbyfloat(const StringView \u0026key, double increment)*** | |\n| **std::chrono::duration** \u003cbr\u003e **std::chrono::time_point** | Time-related parameters | ***bool expire(const StringView \u0026key, const std::chrono::seconds \u0026timeout)*** \u003cbr\u003e ***bool expireat(const StringView \u0026key, const std::chrono::time_point\u003cstd::chrono::system_clock, std::chrono::seconds\u003e \u0026tp)*** | |\n| **std::pair\u003cStringView, StringView\u003e** | Used for Redis hash's (field, value) pair | ***bool hset(const StringView \u0026key, const std::pair\u003cStringView, StringView\u003e \u0026item)*** | |\n| **std::pair\u003cdouble, double\u003e** | Used for Redis geo's (longitude, latitude) pair | ***OptionalLongLong georadius(const StringView \u0026key, const std::pair\u003cdouble, double\u003e \u0026location, double radius, GeoUnit unit, const StringView \u0026destination, bool store_dist, long long count)*** | |\n| **pair of iterators** | Use a pair of iterators to specify a range of input, so that we can pass the data in a STL container to these methods | ***template \u003c typename Input \u003e*** \u003cbr\u003e ***long long del(Input first, Input last)*** | Throw an exception, if it's an empty range, i.e. *first == last* |\n| **std::initializer_list\u003c T \u003e** | Use an initializer list to specify a batch of input | ***template \u003c typename T \u003e*** \u003cbr\u003e ***long long del(std::initializer_list\u003c T \u003e il)*** | |\n| **some options** | Options for some commands | ***UpdateType***, ***template \u003c typename T \u003e class BoundedInterval*** | See [command_options.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/command_options.h) for details |\n\n##### StringView\n\n[std::string_view](https://en.cppreference.com/w/cpp/string/basic_string_view) is a good choice for read-only string parameter types. `std::string_view` was however only introduced in the C++ 17 standard, so if you build *redis-plus-plus* with the `-std=c++11` (i.e. by specifying `-DREDIS_PLUS_PLUS_CXX_STANDARD=11` with cmake command) or the `-std=c++14` standard, a [simple implementation](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/cxx11/cxx_utils.h) of `std::string_view`, called `StringView`, is available. You could build *redis-plus-plus* with the `-std=c++17` standard (i.e. the default behavior), which will supply `std::string_view` natively. The `StringView` implementation will then be disregarded by aliasing it to `std::string_view`. This is done inside the *redis-plus-plus* library with: `using StringView = std::string_view`.\n\nSince there are conversions from `std::string` and c-style string to `StringView`, you can just pass `std::string` or c-style string to methods that need a `StringView` parameter.\n\n```C++\n// bool Redis::hset(const StringView \u0026key, const StringView \u0026field, const StringView \u0026val)\n\n// Pass c-style string to StringView.\nredis.hset(\"key\", \"field\", \"value\");\n\n// Pass std::string to StringView.\nstd::string key = \"key\";\nstd::string field = \"field\";\nstd::string val = \"val\";\nredis.hset(key, field, val);\n\n// Mix std::string and c-style string.\nredis.hset(key, field, \"value\");\n```\n\n#### Return Type\n\n[Redis protocol](https://redis.io/topics/protocol) defines 5 kinds of replies:\n- *Status Reply*: Also known as *Simple String Reply*. It's a non-binary string reply.\n- *Bulk String Reply*: Binary safe string reply.\n- *Integer Reply*: Signed integer reply. Large enough to hold `long long`.\n- *Array Reply*: (Nested) Array reply.\n- *Error Reply*: Non-binary string reply that gives error info.\n\nAlso these replies might be *NULL*. For instance, when you try to `GET` the value of a nonexistent key, Redis returns a *NULL Bulk String Reply*.\n\nAs we mentioned above, replies are parsed into return values of these methods. The following is a list of return types:\n\n| Return Type | Explaination | Example | Note |\n| :---------: | ------------ | ------- | ---- |\n| **void** | *Status Reply* that should always return a string of \"OK\" | *RENAME*, *SETEX* | |\n| **std::string** | *Status Reply* that NOT always return \"OK\", and *Bulk String Reply* | *PING*, *INFO* | |\n| **bool** | *Integer Reply* that always returns 0 or 1 | *EXPIRE*, *HSET* | See the [Boolean Return Value section](#boolean-return-value) for the meaning of a boolean return value |\n| **long long** | *Integer Reply* that not always return 0 or 1 | *DEL*, *APPEND* | |\n| **double** | *Bulk String Reply* that represents a double | *INCRBYFLOAT*, *ZINCRBY* | |\n| **std::pair** | *Array Reply* with exactly 2 elements. Since the return value is always an array of 2 elements, we return the 2 elements as a `std::pair`'s first and second elements | *BLPOP* | |\n| **std::tuple** | *Array Reply* with fixed length and has more than 2 elements. Since length of the returned array is fixed, we return the array as a `std::tuple` | *BZPOPMAX* | |\n| **output iterator** | General *Array Reply* with non-fixed/dynamic length. We use STL-like interface to return this kind of array replies, so that you can insert the return value into a STL container easily | *MGET*, *LRANGE* | Also, sometimes the type of output iterator decides which options to send with the command. See the [Examples section](#command-overloads) for details |\n| **Optional\u003c T \u003e** | For any reply of type `T` that might be *NULL* | *GET*, *LPOP*, *BLPOP*, *BZPOPMAX* | See the [Optional section](#optional) for details on `Optional\u003cT\u003e` |\n| **Variant\u003c Args... \u003e** | For reply that might be of serval different types | *MEMORY STATS* | NOTE: so far, this type is only supported when compiling redis-plus-plus with C++ 17 standard. This is normally used with [generic command interface](https://github.com/sewenew/redis-plus-plus#generic-command-interface). See the [Variant section](#variant) for details on `Variant\u003cArgs...\u003e` |\n| **STL container** | General *Array Reply* | *CONFIG GET* | Both *output iterator* and *STL container* are used for array reply. The difference is that *STL container* is normally used with [generic command interface](https://github.com/sewenew/redis-plus-plus#generic-command-interface). See the [STL container section](#stl-container) for example |\n\n##### Boolean Return Value\n\nThe return type of some methods, e.g. `EXPIRE`, `HSET`, is `bool`. If the method returns `false`, it DOES NOT mean that `Redis` failed to send the command to Redis server. Instead, it means that Redis server returns an *Integer Reply*, and the value of the reply is `0`. Accordingly, if the method returns `true`, it means that Redis server returns an *Integer Reply*, and the value of the reply is `1`. You can \ncheck [Redis commands manual](https://redis.io/commands) for what do `0` and `1` stand for.\n\nFor example, when we send `EXPIRE` command to Redis server, it returns `1` if the timeout was set, and it returns `0` if the key doesn't exist. Accordingly, if the timeout was set, `Redis::expire` returns `true`, and if the key doesn't exist, `Redis::expire` returns `false`.\n\nSo, never use the return value to check if the command has been successfully sent to Redis server. Instead, if `Redis` failed to send command to server, it throws an exception of type `Error`. See the [Exception section](#exception) for details on exceptions.\n\n##### Optional\n\n[std::optional](https://en.cppreference.com/w/cpp/utility/optional) is a good option for return type, if Redis might return *NULL REPLY*. However, `std::optional` is introduced in C++ 17 standard, and if you build *redis-plus-plus* with `-std=c++11` standard (i.e. by specifying `-DREDIS_PLUS_PLUS_CXX_STANDARD=11` with cmake command), we implement our own [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/cxx11/cxx_utils.h), i.e. `template Optional\u003cT\u003e`. Instead, if you build *redis-plus-plus* with `-std=c++17` standard (i.e. the default behavior), you can use `std::optional`, and we have an alias for it: `template \u003ctypename T\u003e using Optional = std::optional\u003cT\u003e`.\n\nTake the [GET](https://redis.io/commands/get) and [MGET](https://redis.io/commands/mget) commands for example:\n\n```C++\n// Or just: auto val = redis.get(\"key\");\nOptional\u003cstd::string\u003e val = redis.get(\"key\");\n\n// Optional\u003cT\u003e has a conversion to bool.\n// If it's NOT a null Optional\u003cT\u003e object, it's converted to true.\n// Otherwise, it's converted to false.\nif (val) {\n    // Key exists. Dereference val to get the string result.\n    std::cout \u003c\u003c *val \u003c\u003c std::endl;\n} else {\n    // Redis server returns a NULL Bulk String Reply.\n    // It's invalid to dereference a null Optional\u003cT\u003e object.\n    std::cout \u003c\u003c \"key doesn't exist.\" \u003c\u003c std::endl;\n}\n\nstd::vector\u003cOptional\u003cstd::string\u003e\u003e values;\nredis.mget({\"key1\", \"key2\", \"key3\"}, std::back_inserter(values));\nfor (const auto \u0026val : values) {\n    if (val) {\n        // Key exist, process the value.\n    }\n}\n```\n\nWe also have some typedefs for some commonly used `Optional\u003cT\u003e`:\n\n```C++\nusing OptionalString = Optional\u003cstd::string\u003e;\n\nusing OptionalLongLong = Optional\u003clong long\u003e;\n\nusing OptionalDouble = Optional\u003cdouble\u003e;\n\nusing OptionalStringPair = Optional\u003cstd::pair\u003cstd::string, std::string\u003e\u003e;\n```\n\n##### Variant\n\n[std::variant](https://en.cppreference.com/w/cpp/utility/variant) is a good option for return type, if the reply might be of different types. For example, the `MEMORY STATS` command returns an array reply, which is, in fact, a map of key-value pairs of configurations:\n\n```shell\n127.0.0.1:6379\u003e memory stats\n 1) \"peak.allocated\"\n 2) (integer) 4471104\n ...\n17) \"db.0\"\n18) 1) \"overhead.hashtable.main\"\n    2) (integer) 104\n    3) \"overhead.hashtable.expires\"\n    4) (integer) 32\n...\n27) \"dataset.percentage\"\n28) \"9.70208740234375\"\n...\n```\n\nHowever, as you can see, the value part of the result might be of type long long (key: *peak.allocated*), double (key: *dataset.percentage*) or even a map (key: *db.0*). So you cannot simply parse the result into a `std::unordered_map\u003cstd::string, long long\u003e` or `std::unordered_map\u003cstd::string, double\u003e`. A workaround is to parse the result into a `tuple`, however, this tuple solution is ugly and error-prone. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/138) for detail.\n\nIn this case, `Variant`, which is a typedef of `std::variant` if you build redis-plus-plus with C++17 standard, is very helpful. You can parse the result into a `std::unordered_map\u003cstd::string, Variant\u003cdouble, long long, std::unordered_map\u003cstd::string, long long\u003e\u003e\u003e`.\n\n```c++\nusing Var = Variant\u003cdouble, long long, std::unordered_map\u003cstd::string, long long\u003e\u003e;\nauto r = Redis(\"tcp://127.0.0.1\");\nauto v = r.command\u003cstd::unordered_map\u003cstd::string, Var\u003e\u003e(\"memory\", \"stats\");\n```\n\nThere're some limitations on `Variant` support:\n\n- The type arguments of `Variant`, cannot have duplicate items, e.g. `Variant\u003cdouble, long long, double\u003e` won't work.\n- `double` must be placed before `std::string`. Because `double` reply is, in fact, string reply, and when parsing variant, we try to parse the reply into the first matched type, specified with the type arguments from left to right. So if `double` is placed after `std::string`, i.e. on the right side of `std::string`, the reply will always be parsed into `std::string`.\n\nAlso check the [generic command section](https://github.com/sewenew/redis-plus-plus#generic-command-interface) for more examples on generic command interface.\n\n##### STL container\n\nWhen using generic command interface, instead of parsing the reply to output iterator, you can also parse it into a STL container.\n\n```c++\nauto r = Redis(\"tcp://127.0.0.1\");\nauto v = r.command\u003cstd::unordered_map\u003cstd::string, std::string\u003e\u003e(\"config\", \"get\", \"*\");\n```\n\nAlso check the [generic command section](https://github.com/sewenew/redis-plus-plus#generic-command-interface) for more examples on generic command interface.\n\n#### Examples\n\nLet's see some examples on how to send commands to Redis server.\n\n##### Various Parameter Types\n\n```C++\n// ***** Parameters of StringView type *****\n\n// Implicitly construct StringView with c-style string.\nredis.set(\"key\", \"value\");\n\n// Implicitly construct StringView with std::string.\nstd::string key(\"key\");\nstd::string val(\"value\");\nredis.set(key, val);\n\n// Explicitly pass StringView as parameter.\nstd::vector\u003cchar\u003e large_data;\n// Avoid copying.\nredis.set(\"key\", StringView(large_data.data(), large_data.size()));\n\n// ***** Parameters of long long type *****\n\n// For index.\nredis.bitcount(key, 1, 3);\n\n// For number.\nredis.incrby(\"num\", 100);\n\n// ***** Parameters of double type *****\n\n// For score.\nredis.zadd(\"zset\", \"m1\", 2.5);\nredis.zadd(\"zset\", \"m2\", 3.5);\nredis.zadd(\"zset\", \"m3\", 5);\n\n// For (longitude, latitude).\nredis.geoadd(\"geo\", std::make_tuple(\"member\", 13.5, 15.6));\n\n// ***** Time-related parameters *****\n\nusing namespace std::chrono;\n\nredis.expire(key, seconds(1000));\n\nauto tp = time_point_cast\u003cseconds\u003e(system_clock::now() + seconds(100));\nredis.expireat(key, tp);\n\n// ***** Some options for commands *****\n\nif (redis.set(key, \"value\", milliseconds(100), UpdateType::NOT_EXIST)) {\n    std::cout \u003c\u003c \"set OK\" \u003c\u003c std::endl;\n}\n\nredis.linsert(\"list\", InsertPosition::BEFORE, \"pivot\", \"val\");\n\nstd::vector\u003cstd::string\u003e res;\n\n// (-inf, inf)\nredis.zrangebyscore(\"zset\", UnboundedInterval\u003cdouble\u003e{}, std::back_inserter(res));\n\n// [3, 6]\nredis.zrangebyscore(\"zset\",\n    BoundedInterval\u003cdouble\u003e(3, 6, BoundType::CLOSED),\n    std::back_inserter(res));\n\n// (3, 6]\nredis.zrangebyscore(\"zset\",\n    BoundedInterval\u003cdouble\u003e(3, 6, BoundType::LEFT_OPEN),\n    std::back_inserter(res));\n\n// (3, 6)\nredis.zrangebyscore(\"zset\",\n    BoundedInterval\u003cdouble\u003e(3, 6, BoundType::OPEN),\n    std::back_inserter(res));\n\n// [3, 6)\nredis.zrangebyscore(\"zset\",\n    BoundedInterval\u003cdouble\u003e(3, 6, BoundType::RIGHT_OPEN),\n    std::back_inserter(res));\n\n// [3, +inf)\nredis.zrangebyscore(\"zset\",\n    LeftBoundedInterval\u003cdouble\u003e(3, BoundType::RIGHT_OPEN),\n    std::back_inserter(res));\n\n// (3, +inf)\nredis.zrangebyscore(\"zset\",\n    LeftBoundedInterval\u003cdouble\u003e(3, BoundType::OPEN),\n    std::back_inserter(res));\n\n// (-inf, 6]\nredis.zrangebyscore(\"zset\",\n    RightBoundedInterval\u003cdouble\u003e(6, BoundType::LEFT_OPEN),\n    std::back_inserter(res));\n\n// (-inf, 6)\nredis.zrangebyscore(\"zset\",\n    RightBoundedInterval\u003cdouble\u003e(6, BoundType::OPEN),\n    std::back_inserter(res));\n\n// ***** Pair of iterators *****\n\nstd::vector\u003cstd::pair\u003cstd::string, std::string\u003e\u003e kvs = {{\"k1\", \"v1\"}, {\"k2\", \"v2\"}, {\"k3\", \"v3\"}};\nredis.mset(kvs.begin(), kvs.end());\n\nstd::unordered_map\u003cstd::string, std::string\u003e kv_map = {{\"k1\", \"v1\"}, {\"k2\", \"v2\"}, {\"k3\", \"v3\"}};\nredis.mset(kv_map.begin(), kv_map.end());\n\nstd::unordered_map\u003cstd::string, std::string\u003e str_map = {{\"f1\", \"v1\"}, {\"f2\", \"v2\"}, {\"f3\", \"v3\"}};\nredis.hmset(\"hash\", str_map.begin(), str_map.end());\n\nstd::unordered_map\u003cstd::string, double\u003e score_map = {{\"m1\", 20}, {\"m2\", 12.5}, {\"m3\", 3.14}};\nredis.zadd(\"zset\", score_map.begin(), score_map.end());\n\nstd::vector\u003cstd::string\u003e keys = {\"k1\", \"k2\", \"k3\"};\nredis.del(keys.begin(), keys.end());\n\n// ***** Parameters of initializer_list type *****\n\nredis.mset({\n    std::make_pair(\"k1\", \"v1\"),\n    std::make_pair(\"k2\", \"v2\"),\n    std::make_pair(\"k3\", \"v3\")\n});\n\nredis.hmset(\"hash\",\n    {\n        std::make_pair(\"f1\", \"v1\"),\n        std::make_pair(\"f2\", \"v2\"),\n        std::make_pair(\"f3\", \"v3\")\n    });\n\nredis.zadd(\"zset\",\n    {\n        std::make_pair(\"m1\", 20.0),\n        std::make_pair(\"m2\", 34.5),\n        std::make_pair(\"m3\", 23.4)\n    });\n\nredis.del({\"k1\", \"k2\", \"k3\"});\n```\n\n##### Various Return Types\n\n```C++\n// ***** Return void *****\n\nredis.save();\n\n// ***** Return std::string *****\n\nauto info = redis.info();\n\n// ***** Return bool *****\n\nif (!redis.expire(\"nonexistent\", std::chrono::seconds(100))) {\n    std::cerr \u003c\u003c \"key doesn't exist\" \u003c\u003c std::endl;\n}\n\nif (redis.setnx(\"key\", \"val\")) {\n    std::cout \u003c\u003c \"set OK\" \u003c\u003c std::endl;\n}\n\n// ***** Return long long *****\n\nauto len = redis.strlen(\"key\");\nauto num = redis.del({\"a\", \"b\", \"c\"});\nnum = redis.incr(\"a\");\n\n// ***** Return double *****\n\nauto real = redis.incrbyfloat(\"b\", 23.4);\nreal = redis.hincrbyfloat(\"c\", \"f\", 34.5);\n\n// ***** Return Optional\u003cstd::string\u003e, i.e. OptionalString *****\n\nauto os = redis.get(\"kk\");\nif (os) {\n    std::cout \u003c\u003c *os \u003c\u003c std::endl;\n} else {\n    std::cerr \u003c\u003c \"key doesn't exist\" \u003c\u003c std::endl;\n}\n\nos = redis.spop(\"set\");\nif (os) {\n    std::cout \u003c\u003c *os \u003c\u003c std::endl;\n} else {\n    std::cerr \u003c\u003c \"set is empty\" \u003c\u003c std::endl;\n}\n\n// ***** Return Optional\u003clong long\u003e, i.e. OptionalLongLong *****\n\nauto oll = redis.zrank(\"zset\", \"mem\");\nif (oll) {\n    std::cout \u003c\u003c \"rank is \" \u003c\u003c *oll \u003c\u003c std::endl;\n} else {\n    std::cerr \u003c\u003c \"member doesn't exist\" \u003c\u003c std::endl;\n}\n\n// ***** Return Optional\u003cdouble\u003e, i.e. OptionalDouble *****\n\nauto ob = redis.zscore(\"zset\", \"m1\");\nif (ob) {\n    std::cout \u003c\u003c \"score is \" \u003c\u003c *ob \u003c\u003c std::endl;\n} else {\n    std::cerr \u003c\u003c \"member doesn't exist\" \u003c\u003c std::endl;\n}\n\n// ***** Return Optional\u003cpair\u003cstring, string\u003e\u003e *****\n\nauto op = redis.blpop({\"list1\", \"list2\"}, std::chrono::seconds(2));\nif (op) {\n    std::cout \u003c\u003c \"key is \" \u003c\u003c op-\u003efirst \u003c\u003c \", value is \" \u003c\u003c op-\u003esecond \u003c\u003c std::endl;\n} else {\n    std::cerr \u003c\u003c \"timeout\" \u003c\u003c std::endl;\n}\n\n// ***** Output iterators *****\n\nstd::vector\u003cOptionalString\u003e os_vec;\nredis.mget({\"k1\", \"k2\", \"k3\"}, std::back_inserter(os_vec));\n\nstd::vector\u003cstd::string\u003e s_vec;\nredis.lrange(\"list\", 0, -1, std::back_inserter(s_vec));\n\nstd::unordered_map\u003cstd::string, std::string\u003e hash;\nredis.hgetall(\"hash\", std::inserter(hash, hash.end()));\n// You can also save the result in a vecotr of string pair.\nstd::vector\u003cstd::pair\u003cstd::string, std::string\u003e\u003e hash_vec;\nredis.hgetall(\"hash\", std::back_inserter(hash_vec));\n\nstd::unordered_set\u003cstd::string\u003e str_set;\nredis.smembers(\"s1\", std::inserter(str_set, str_set.end()));\n// You can also save the result in a vecotr of string.\ns_vec.clear();\nredis.smembers(\"s1\", std::back_inserter(s_vec));\n```\n\n##### SCAN Commands\n\n```C++\nsw::redis::Cursor cursor = 0;\nauto pattern = \"*pattern*\";\nauto count = 5;\nstd::unordered_set\u003cstd::string\u003e keys;\nwhile (true) {\n    cursor = redis.scan(cursor, pattern, count, std::inserter(keys, keys.begin()));\n    // Default pattern is \"*\", and default count is 10\n    // cursor = redis.scan(cursor, std::inserter(keys, keys.begin()));\n\n    if (cursor == 0) {\n        break;\n    }\n}\n```\n\n##### Command Overloads\n\nSometimes the type of output iterator decides which options to send with the command.\n\n```C++\n// If the output iterator is an iterator of a container of string,\n// we send *ZRANGE* command without the *WITHSCORES* option.\nstd::vector\u003cstd::string\u003e members;\nredis.zrange(\"list\", 0, -1, std::back_inserter(members));\n\n// If it's an iterator of a container of a \u003cstring, double\u003e pair,\n// we send *ZRANGE* command with *WITHSCORES* option.\nstd::vector\u003cstd::pair\u003cstd::string, double\u003e\u003e res_with_score;\nredis.zrange(\"list\", 0, -1, std::back_inserter(res_with_score));\n\n// The above examples also apply to other command with the *WITHSCORES* options,\n// e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*.\n\n// Another example is the *GEORADIUS* command.\n\n// Only get members.\nmembers.clear();\nredis.georadius(\"geo\",\n            std::make_pair(10.1, 11.1),\n            100,\n            GeoUnit::KM,\n            10,\n            true,\n            std::back_inserter(members));\n\n// If the iterator is an iterator of a container of tuple\u003cstring, double\u003e,\n// we send the *GEORADIUS* command with *WITHDIST* option.\nstd::vector\u003cstd::tuple\u003cstd::string, double\u003e\u003e mem_with_dist;\nredis.georadius(\"geo\",\n            std::make_pair(10.1, 11.1),\n            100,\n            GeoUnit::KM,\n            10,\n            true,\n            std::back_inserter(mem_with_dist));\n\n// If the iterator is an iterator of a container of tuple\u003cstring, double, string\u003e,\n// we send the *GEORADIUS* command with *WITHDIST* and *WITHHASH* options.\nstd::vector\u003cstd::tuple\u003cstd::string, double, std::string\u003e\u003e mem_with_dist_hash;\nredis.georadius(\"geo\",\n            std::make_pair(10.1, 11.1),\n            100,\n            GeoUnit::KM,\n            10,\n            true,\n            std::back_inserter(mem_with_dist_hash));\n\n// If the iterator is an iterator of a container of\n// tuple\u003cstring, string, pair\u003cdouble, double\u003e, double\u003e,\n// we send the *GEORADIUS* command with *WITHHASH*, *WITHCOORD* and *WITHDIST* options.\nstd::vector\u003cstd::tuple\u003cstd::string, double, std::string\u003e\u003e mem_with_hash_coord_dist;\nredis.georadius(\"geo\",\n            std::make_pair(10.1, 11.1),\n            100,\n            GeoUnit::KM,\n            10,\n            true,\n            std::back_inserter(mem_with_hash_coord_dist));\n```\n\nPlease see [redis.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/redis.h) for doxygen style API references and examples, and see the [tests](https://github.com/sewenew/redis-plus-plus/tree/master/test/src/sw/redis%2B%2B) for other examples.\n\n### Exception\n\n`Redis` throws exceptions if it receives an *Error Reply* or something bad happens, e.g. failed to create a connection to server, or connection to server is broken. All exceptions derived from `Error` class. See [errors.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/errors.h) for details.\n\n- `Error`: Generic error. It's derived from `std::exception`, and it's also the base class of other exceptions.\n- `IoError`: There's some IO error with the connection.\n- `TimeoutError`: Read or write operation was timed out. It's a derived class of `IoError`.\n- `ClosedError`: Redis server closed the connection.\n- `ProtoError`: The command or reply is invalid, and we cannot process it with Redis protocol.\n- `OomError`: *hiredis* library got an out-of-memory error.\n- `ReplyError`: Redis server returned an error reply, e.g. we try to call `redis::lrange` on a Redis hash.\n- `WatchError`: Watched key has been modified. See [Watch section](#watch) for details.\n\n**NOTE**: *NULL REPLY* is not taken as an exception. For example, if we try to `GET` a non-existent key, we'll get a *NULL Bulk String Reply*. Instead of throwing an exception, we return the *NULL REPLY* as a null `Optional\u003cT\u003e` object. Also see [Optional section](#optional).\n\nNormally, when exception happens, you don't need to create a `Redis` object. It's exception safe, and you can reuse the `Redis` object. Even if the connection to Redis server is broken, and it throws some exception, say, `IoError`. The next time when you send command with the `Redis` object, it will try to reconnect to Redis server automatically. This rule also applies to `RedisCluster`. However, if `Pipeline`, `Transcation` and `Subscriber` throws exception, you need to destroy the object, and create a new one. See the corresponding documentation for details.\n\n#### Examples\n\nThe following is an example on how to catch these exceptions:\n\n```c++\ntry {\n    redis.set(\"key\", \"value\");\n\n    // Wrong type error\n    redis.lpush(\"key\", {\"a\", \"b\", \"c\"});\n} catch (const ReplyError \u0026err) {\n    // WRONGTYPE Operation against a key holding the wrong kind of value\n    cout \u003c\u003c err.what() \u003c\u003c endl;\n} catch (const TimeoutError \u0026err) {\n    // reading or writing timeout\n} catch (const ClosedError \u0026err) {\n    // the connection has been closed.\n} catch (const IoError \u0026err) {\n    // there's an IO error on the connection.\n} catch (const Error \u0026err) {\n   // other errors\n}\n```\n\n### Generic Command Interface\n\nThere're too many Redis commands, we haven't implemented all of them. However, you can use the generic `Redis::command` methods to send any commands to Redis. Unlike other client libraries, `Redis::command` doesn't use format string to combine command arguments into a command string. Instead, you can directly pass command arguments of `StringView` type or arithmetic type as parameters of `Redis::command`. For the reason why we don't use format string, please see [this discussion](https://github.com/sewenew/redis-plus-plus/pull/2).\n\n```C++\nauto redis = Redis(\"tcp://127.0.0.1\");\n\n// Redis class doesn't have built-in *CLIENT SETNAME* method.\n// However, you can use Redis::command to send the command manually.\nredis.command\u003cvoid\u003e(\"client\", \"setname\", \"name\");\nauto val = redis.command\u003cOptionalString\u003e(\"client\", \"getname\");\nif (val) {\n    std::cout \u003c\u003c *val \u003c\u003c std::endl;\n}\n\n// NOTE: the following code is for example only. In fact, Redis has built-in\n// methods for the following commands.\n\n// Arguments of the command can be strings.\n// NOTE: for SET command, the return value is NOT always void, I'll explain latter.\nredis.command\u003cvoid\u003e(\"set\", \"key\", \"100\");\n\n// Arguments of the command can be a combination of strings and integers.\nauto num = redis.command\u003clong long\u003e(\"incrby\", \"key\", 1);\n\n// Argument can also be double.\nauto real = redis.command\u003cdouble\u003e(\"incrbyfloat\", \"key\", 2.3);\n\n// Even the key of the command can be of arithmetic type.\nredis.command\u003cvoid\u003e(\"set\", 100, \"value\");\n\nval = redis.command\u003cOptionalString\u003e(\"get\", 100);\n\n// If the command returns an array of elements.\nstd::vector\u003cOptionalString\u003e result;\nredis.command(\"mget\", \"k1\", \"k2\", \"k3\", std::back_inserter(result));\n\n// Or just parse it into a vector.\nresult = redis.command\u003cstd::vector\u003cOptionalString\u003e\u003e(\"mget\", \"k1\", \"k2\", \"k3\");\n\n// Arguments of the command can be a range of strings.\nauto set_cmd_strs = {\"set\", \"key\", \"value\"};\nredis.command\u003cvoid\u003e(set_cmd_strs.begin(), set_cmd_strs.end());\n\nauto get_cmd_strs = {\"get\", \"key\"};\nval = redis.command\u003cOptionalString\u003e(get_cmd_strs.begin(), get_cmd_strs.end());\n\n// If it returns an array of elements.\nresult.clear();\nauto mget_cmd_strs = {\"mget\", \"key1\", \"key2\"};\nredis.command(mget_cmd_strs.begin(), mget_cmd_strs.end(), std::back_inserter(result));\n```\n\n**NOTE**: The name of some Redis commands is composed with two strings, e.g. *CLIENT SETNAME*. In this case, you need to pass these two strings as two arguments for `Redis::command`.\n\n```C++\n// This is GOOD.\nredis.command\u003cvoid\u003e(\"client\", \"setname\", \"name\");\n\n// This is BAD, and will fail to send command to Redis server.\n// redis.command\u003cvoid\u003e(\"client setname\", \"name\");\n```\n\nAs I mentioned in the comments, the `SET` command not always returns `void`. Because if you try to set a (key, value) pair with *NX* or *XX* option, you might fail, and Redis will return a *NULL REPLY*. Besides the `SET` command, there're other commands whose return value is NOT a fixed type, you need to parse it by yourself. For example, `Redis::set` method rewrite the reply of `SET` command, and make it return `bool` type, i.e. if no *NX* or *XX* option specified, Redis server will always return an \"OK\" string, and `Redis::set` returns `true`; if *NX* or *XX* specified, and Redis server returns a *NULL REPLY*, `Redis::set` returns `false`.\n\nSo `Redis` class also has other overloaded `command` methods, these methods return a `ReplyUPtr`, i.e. `std::unique_ptr\u003credisReply, ReplyDeleter\u003e`, object. Normally you don't need to parse it manually. Instead, you only need to pass the reply to `template \u003ctypename T\u003e T reply::parse(redisReply \u0026)` to get a value of type `T`. Check the [Return Type section](#return-type) for valid `T` types. If the command returns an array of elements, besides calling `reply::parse` to parse the reply to an STL container, you can also call `template \u003ctypename Output\u003e reply::to_array(redisReply \u0026reply, Output output)` to parse the result into an array or STL container with an output iterator.\n\nLet's rewrite the above examples:\n\n```C++\nauto redis = Redis(\"tcp://127.0.0.1\");\n\nredis.command(\"client\", \"setname\", \"name\");\nauto r = redis.command(\"client\", \"getname\");\nassert(r);\n\n// If the command returns a single element,\n// use `reply::parse\u003cT\u003e(redisReply\u0026)` to parse it.\nauto val = reply::parse\u003cOptionalString\u003e(*r);\nif (val) {\n    std::cout \u003c\u003c *val \u003c\u003c std::endl;\n}\n\n// Arguments of the command can be strings.\nredis.command(\"set\", \"key\", \"100\");\n\n// Arguments of the command can be a combination of strings and integers.\nr = redis.command(\"incrby\", \"key\", 1);\nauto num = reply::parse\u003clong long\u003e(*r);\n\n// Argument can also be double.\nr = redis.command(\"incrbyfloat\", \"key\", 2.3);\nauto real = reply::parse\u003cdouble\u003e(*r);\n\n// Even the key of the command can be of arithmetic type.\nredis.command(\"set\", 100, \"value\");\n\nr = redis.command(\"get\", 100);\nval = reply::parse\u003cOptionalString\u003e(*r);\n\n// If the command returns an array of elements.\nr = redis.command(\"mget\", \"k1\", \"k2\", \"k3\");\n// Use `reply::to_array(redisReply\u0026, OutputIterator)` to parse the result into an STL container.\nstd::vector\u003cOptionalString\u003e result;\nreply::to_array(*r, std::back_inserter(result));\n\n// Or just call `reply::parse` to parse it into vector.\nresult = reply::parse\u003cstd::vector\u003cOptionalString\u003e\u003e(*r);\n\n// Arguments of the command can be a range of strings.\nauto get_cmd_strs = {\"get\", \"key\"};\nr = redis.command(get_cmd_strs.begin(), get_cmd_strs.end());\nval = reply::parse\u003cOptionalString\u003e(*r);\n\n// If it returns an array of elements.\nresult.clear();\nauto mget_cmd_strs = {\"mget\", \"key1\", \"key2\"};\nr = redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end());\nreply::to_array(*r, std::back_inserter(result));\n```\n\nIn fact, there's one more `Redis::command` method:\n\n```C++\ntemplate \u003ctypename Cmd, typename ...Args\u003e\nauto command(Cmd cmd, Args \u0026\u0026...args)\n    -\u003e typename std::enable_if\u003c!std::is_convertible\u003cCmd, StringView\u003e::value, ReplyUPtr\u003e::type;\n```\n\nHowever, this method exposes some implementation details, and is only for internal use. You should NOT use this method.\n\n### Publish/Subscribe\n\nYou can use `Redis::publish` to publish messages to channels. `Redis` randomly picks a connection from the underlying connection pool, and publishes message with that connection. So you might publish two messages with two different connections.\n\nWhen you subscribe to a channel with a connection, all messages published to the channel are sent back to that connection. So there's NO `Redis::subscribe` method. Instead, you can call `Redis::subscriber` to create a `Subscriber` and the `Subscriber` maintains a connection to Redis. The underlying connection is a new connection, NOT picked from the connection pool. This new connection has the same `ConnectionOptions` as the `Redis` object.\n\nIf you want to have different connection options, e.g. `ConnectionOptions::socket_timeout`, for different channels, you should create `Redis` objects with different connection options, then you can create `Subscriber` objects with these `Redis` objects. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/307#issuecomment-1002015671) for a use case.\n\n```c++\nConnectionOptions opts1;\nopts1.host = \"127.0.0.1\";\nopts1.port = 6379;\nopts1.socket_timeout = std::chrono::milliseconds(100);\n\nauto redis1 = Redis(opts1);\n\n// sub1's socket_timeout is 100ms.\nauto sub1 = redis1.subscriber();\n\nConnectionOptions opts2;\nopts2.host = \"127.0.0.1\";\nopts2.port = 6379;\nopts2.socket_timeout = std::chrono::milliseconds(300);\n\nauto redis2 = Redis(opts2);\n\n// sub2's socket_timeout is 300ms.\nauto sub2 = redis2.subscriber();\n```\n\n**NOTE**: Although the above code creates two `Redis` objects, it has no performance penalty. Because `Redis` object creates connections lazily, i.e. no connection will be created until we send some command with `Redis` object, and the connection is created only when we call `Redis::subscriber` to create `Subscriber` object.\n\nWith `Subscriber`, you can call `Subscriber::subscribe`, `Subscriber::unsubscribe`, `Subscriber::psubscribe` and `Subscriber::punsubscribe` to send *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* commands to Redis.\n\n#### Thread Safety\n\n`Subscriber` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually.\n\n#### Exception\n\nIf any of the `Subscriber`'s method throws an exception other than `ReplyError` or `TimeoutError`, you CANNOT use it any more. Instead, you have to destroy the `Subscriber` object, and create a new one.\n\n#### Subscriber Callbacks\n\nThere are 6 kinds of messages:\n- *MESSAGE*: message sent to a channel.\n- *PMESSAGE*: message sent to channels of a given pattern.\n- *SUBSCRIBE*: message sent when we successfully subscribe to a channel.\n- *UNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel.\n- *PSUBSCRIBE*: message sent when we successfully subscribe to a channel pattern.\n- *PUNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel pattern.\n\nWe call messages of *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* types as *META MESSAGE*s.\n\nIn order to process these messages, you can set callback functions on `Subscriber`:\n- `Subscriber::on_message(MsgCallback)`: set callback function for messages of *MESSAGE* type, and the callback interface is: `void (std::string channel, std::string msg)`.\n- `Subscriber::on_pmessage(PatternMsgCallback)`: set the callback function for messages of *PMESSAGE* type, and the callback interface is: `void (std::string pattern, std::string channel, std::string msg)`.\n- `Subscriber::on_meta(MetaCallback)`: set callback function for messages of *META MESSAGE* type, and the callback interface is: `void (Subscriber::MsgType type, OptionalString channel, long long num)`. `type` is an enum, it can be one of the following enum: `Subscriber::MsgType::SUBSCRIBE`, `Subscriber::MsgType::UNSUBSCRIBE`, `Subscriber::MsgType::PSUBSCRIBE`, `Subscriber::MsgType::PUNSUBSCRIBE`, `Subscriber::MsgType::MESSAGE`, and `Subscriber::MsgType::PMESSAGE`. If you haven't subscribe/psubscribe to any channel/pattern, and try to unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all channels/patterns, *channel* will be null. So the second parameter of meta callback is of type `OptionalString`.\n\nAll these callback interfaces pass `std::string` by value, and you can take their ownership (i.e. `std::move`) safely.\n\n#### Consume Messages\n\nYou can call `Subscriber::consume` to consume messages published to channels/patterns that the `Subscriber` has been subscribed.\n\n`Subscriber::consume` waits for message from the underlying connection. If the `ConnectionOptions::socket_timeout` is reached, and there's no message sent to this connection, `Subscriber::consume` throws a `TimeoutError` exception. If `ConnectionOptions::socket_timeout` is `0ms`, `Subscriber::consume` blocks until it receives a message.\n\nAfter receiving the message, `Subscriber::consume` calls the callback function to process the message based on message type. However, if you don't set callback for a specific kind of message, `Subscriber::consume` will consume the received message and discard it, i.e. `Subscriber::consume` returns without running the callback.\n\n#### Examples\n\nThe following example is a common pattern for using `Subscriber`:\n\n```C++\n// Create a Subscriber.\nauto sub = redis.subscriber();\n\n// Set callback functions.\nsub.on_message([](std::string channel, std::string msg) {\n            // Process message of MESSAGE type.\n        });\n\nsub.on_pmessage([](std::string pattern, std::string channel, std::string msg) {\n            // Process message of PMESSAGE type.\n        });\n\nsub.on_meta([](Subscriber::MsgType type, OptionalString channel, long long num) {\n            // Process message of META type.\n        });\n\n// Subscribe to channels and patterns.\nsub.subscribe(\"channel1\");\nsub.subscribe({\"channel2\", \"channel3\"});\n\nsub.psubscribe(\"pattern1*\");\n\n// Consume messages in a loop.\nwhile (true) {\n    try {\n        sub.consume();\n    } catch (const Error \u0026err) {\n        // Handle exceptions.\n    }\n}\n```\n\nIf `ConnectionOptions::socket_timeout` is set, you might get `TimeoutError` exception before receiving a message:\n\n```C++\nwhile (true) {\n    try {\n        sub.consume();\n    } catch (const TimeoutError \u0026e) {\n        // Try again.\n        continue;\n    } catch (const Error \u0026err) {\n        // Handle other exceptions.\n    }\n}\n```\n\nThe above examples use lambda as callback. If you're not familiar with lambda, you can also set a free function as callback. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/16) for detail.\n\n### Pipeline\n\n[Pipeline](https://redis.io/topics/pipelining) is used to reduce *RTT* (Round Trip Time), and speed up Redis queries. *redis-plus-plus* supports pipeline with the `Pipeline` class.\n\n#### Create Pipeline\n\nYou can create a pipeline with `Redis::pipeline` method, which returns a `Pipeline` object.\n\n```C++\nConnectionOptions connection_options;\nConnectionPoolOptions pool_options;\n\nRedis redis(connection_options, pool_options);\n\nauto pipe = redis.pipeline();\n```\n\nWhen creating a `Pipeline` object, by default, `Redis::pipeline` method creates a new connection to Redis server. This connection is NOT picked from the connection pool, but a newly created connection. This connection has the same `ConnectionOptions` as other connections in the connection pool. `Pipeline` object maintains the new connection, and all piped commands are sent through this connection.\n\n**NOTE**: By default, creating a `Pipeline` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Pipeline` object as much as possible. Check [this](#create-pipeline-without-creating-new-connection) to see how to create a `Pipeline` object without creating a new connection.\n\n#### Send Commands\n\nYou can send Redis commands through the `Pipeline` object. Just like the `Redis` class, `Pipeline` has one or more (overloaded) methods for each Redis command. However, you CANNOT get the replies until you call `Pipeline::exec`. So these methods do NOT return the reply, instead they return the `Pipeline` object itself. And you can chain these methods calls.\n\n```C++\npipe.set(\"key\", \"val\").incr(\"num\").rpush(\"list\", {0, 1, 2}).command(\"hset\", \"key\", \"field\", \"value\");\n```\n\n#### Get Replies\n\nOnce you finish sending commands to Redis, you can call `Pipeline::exec` to get replies of these commands. You can also chain `Pipeline::exec` with other commands.\n\n```C++\npipe.set(\"key\", \"val\").incr(\"num\");\nauto replies = pipe.exec();\n\n// The same as:\nreplies = pipe.set(\"key\", \"val\").incr(\"num\").exec();\n```\n\nIn fact, these commands won't be sent to Redis, until you call `Pipeline::exec`. So `Pipeline::exec` does 2 work in order: send all piped commands, then get all replies from Redis.\n\nAlso you can call `Pipeline::discard` to discard those piped commands.\n\n```C++\npipe.set(\"key\", \"val\").incr(\"num\");\n\npipe.discard();\n```\n\n#### Parse Replies\n\n`Pipeline::exec` returns a `QueuedReplies` object, which contains replies of all commands that have been sent to Redis. You can use `QueuedReplies::get` method to get and parse the `ith` reply. It has 3 overloads:\n\n- `template \u003ctypename Result\u003e Result get(std::size_t idx)`: Return the `ith` reply as a return value, and you need to specify the return type as tempalte parameter.\n- `template \u003ctypename Output\u003e void get(std::size_t idx, Output output)`: If the reply is of type *Array Reply*, you can call this method to write the `ith` reply to an output iterator. Normally, compiler will deduce the type of the output iterator, and you don't need to specify the type parameter explicitly.\n- `redisReply\u0026 get(std::size_t idx)`: If the reply is NOT a fixed type, call this method to get a reference to `redisReply` object. In this case, you need to call `template \u003ctypename T\u003e T reply::parse(redisReply \u0026)` to parse the reply manually.\n\nCheck the [Return Type section](#return-type) for details on the return types of the result.\n\n```C++\nauto replies = pipe.set(\"key\", \"val\").incr(\"num\").lrange(\"list\", 0, -1).exec();\n\nauto set_cmd_result = replies.get\u003cbool\u003e(0);\n\nauto incr_cmd_result = replies.get\u003clong long\u003e(1);\n\nstd::vector\u003cstd::string\u003e list_cmd_result;\nreplies.get(2, std::back_inserter(list_cmd_result));\n```\n\n#### Exception\n\nIf any of `Pipeline`'s method throws an exception other than `ReplyError`, the `Pipeline` object enters an invalid state. You CANNOT use it any more, but only destroy the object, and create a new one.\n\n#### Thread Safety\n\n`Pipeline` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually.\n\n#### Create Pipeline Without Creating New Connection\n\n**YOU MUST CAREFULLY READ ALL WORDS IN THIS SECTION AND THE VERY IMPORTANT NOTES BEFORE USING THIS FEATURE!!!**\n\nIn fact, you can also create a `Pipeline` object with a connection from the underlying connection pool, so that calling `Redis::pipeline` method can be much cheaper (since it doesn't need to create a new connection).\n\nThe prototype of `Redis::pipeline` is as follows: `Pipeline pipeline(bool new_connection = true);`. If `new_connection` is false, the `Pipeline` object will be created with a connection from the underlying pool.\n\n```c++\nConnectionOptions connection_options;\nConnectionPoolOptions pool_options;\n\nRedis redis(connection_options, pool_options);\n\n// Create a Pipeline without creating a new connection.\nauto pipe = redis.pipeline(false);\n```\n\n##### VERY IMPORTANT NOTES\n\nHowever, in this case, you MUST be very careful, otherwise, you might get bad performance or even dead lock. Because when you run command with `Pipeline` object, it will hold the connection until `Pipeline::exec`, `Pipeline::discard` or `Pipeline`'s destructor is called (the connection will also be released if any method of `Pipeline` throws `Exception`). If the `Pipeline` object holds the connection for a long time, other `Redis` methods might not be able to get a connection from the underlying pool.\n\nCheck the following dead lock example:\n\n```c++\n// By defaul, create a `Redis` object with only ONE connection in pool.\n// Also by default, the `ConnectionPoolOptions::wait_timeout` is 0ms,\n// which means if the pool is empty, `Redis` method will be blocked until\n// the pool is not empty.\nRedis redis(\"tcp://127.0.0.1\");\n\n// Create a `Pipeline` with a connection in the underlying pool.\n// In fact, the connection hasn't been fetched from the pool\n// until some method of `Pipeline` has been called.\nauto pipe = redis.pipeline(false);\n\n// Now the `Pipeline` object fetches a connection from the pool.\npipe.set(\"key1\", \"val\");\n\n// `Pipeline` object still holds the connection until `Pipeline::exec`,\n// `Pipeline::discard` or the destructor is called.\npipe.set(\"key2\", \"val\");\n\n// Try to send a command with `Redis` object.\n// However, the pool is empty, since the `Pipeline` object still holds\n// the connection, and this call will be blocked forever.\n// DEAD LOCK!!!\nredis.get(\"key\");\n\n// NEVER goes here.\npipe.exec();\n```\n\n**BEST PRACTICE**:\n\nWhen creating `Pipeline` without creating a new connection:\n\n- Always set `ConnectionPoolOptions::wait_timeout` larger than 0ms (i.e. when pool is empty, never block forever).\n- Avoid doing slow operation between `Pipeline`'s methods.\n- Better chain `Pipeline` methods and the `Pipeline::exec` in one statements.\n- Better leave `Pipeline` related code in a block scope.\n\n```c++\nConnectionOptions opts;\nopts.host = \"127.0.0.1\";\nopts.port = 6379;\nopts.socket_timeout = std::chrono::milliseconds(50);\n\nConnectionPoolOptions pool_opts;\npool_opts.size = 3;\n\n// Always set `wait_timeout` larger than 0ms.\npool_opts.wait_timeout = std::chrono::milliseconds(50);\n\nauto redis = Redis(opts, pool_opts);\n\n{\n    // Better put `Pipeline` related code in a block scope.\n    auto pipe = redis.pipeline(false);\n\n    pipe.set(\"key1\", \"val\");\n\n    // DON'T run slow operations here, since `Pipeline` object still holds\n    // the connection, other threads using this `Redis` object, might be blocked.\n\n    pipe.set(\"key2\", \"val\");\n\n    // When `Pipeline::exec` finishes, `Pipeline` releases the connection, and returns it to pool.\n    auto replies = pipe.exec();\n\n    // This is even better, i.e. chain `Pipeline` methods with `Pipeline::exec`.\n    replies = pipe.set(\"key1\", \"val\").set(\"key2\", \"val\").exec();\n}\n\nfor (auto i = 0; i \u003c 10; ++i) {\n    // This operation, i.e. creating a `Pipeline` object with connection in pool, is cheap\n    auto pipe = redis.pipeline(false);\n\n    // Fetch a connection from the underlying pool, and hold it.\n    pipe.set(\"key1\", \"val\").set(\"key2\", \"val\");\n\n    // Although `Pipeline::exec` and `Pipeline::discard` haven't been called,\n    // when `Pipeline`'s destructor is called, the connection will also be\n    // returned to the pool.\n}\n```\n\n### Transaction\n\n[Transaction](https://redis.io/topics/transactions) is used to make multiple commands runs atomically.\n\n#### Create Transaction\n\nYou can create a transaction with `Redis::transaction` method, which returns a `Transaction` object.\n\n```C++\nConnectionOptions connection_options;\nConnectionPoolOptions pool_options;\n\nRedis redis(connection_options, pool_options);\n\nauto tx = redis.transaction();\n```\n\nAs the `Pipeline` class, `Transaction` maintains a newly created connection to Redis. This connection has the same `ConnectionOptions` as the `Redis` object.\n\n**NOTE**: Creating a `Transaction` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Transaction` as much as possible. Check [this](#create-transaction-without-creating-new-connection) to see how to create a `Transaction` object without creating a new connection.\n\nAlso you don't need to send [MULTI](https://redis.io/commands/multi) command to Redis. `Transaction` will do that for you automatically.\n\n#### Send Commands\n\n`Transaction` shares most of implementation with `Pipeline`. It has the same interfaces as `Pipeline`. You can send commands as what you do with `Pipeline` object.\n\n```C++\ntx.set(\"key\", \"val\").incr(\"num\").lpush(\"list\", {0, 1, 2}).command(\"hset\", \"key\", \"field\", \"val\");\n```\n\n#### Execute Transaction\n\nWhen you call `Transaction::exec`, you explicitly ask Redis to execute those queued commands, and return the replies. Otherwise, these commands won't be executed. Also, you can call `Transaction::discard` to discard the execution, i.e. no command will be executed. Both `Transaction::exec` and `Transaction::discard` can be chained with other commands.\n\n```C++\nauto replies = tx.set(\"key\", \"val\").incr(\"num\").exec();\n\ntx.set(\"key\", \"val\").incr(\"num\");\n\n// Discard the transaction.\ntx.discard();\n```\n\n#### Parse Replies\n\nSee [Pipeline's Parse Replies section](#parse-replies) for how to parse the replies.\n\n#### Piped Transaction\n\nNormally, we always send multiple commnds in a transaction. In order to improve the performance, you can send these commands in a pipeline. You can create a piped transaction by passing `true` as parameter of `Redis::transaction` method.\n\n```C++\n// Create a piped transaction\nauto tx = redis.transaction(true);\n```\n\nWith this piped transaction, all commands are sent to Redis in a pipeline.\n\n#### Exception\n\nIf any of `Transaction`'s method throws an exception other than `WatchError` or `ReplyError`, the `Transaction` object enters an invalid state. You CANNOT use it any more, but only destroy the object and create a new one.\n\n#### Thread Safety\n\n`Transacation` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually.\n\n#### Watch\n\n[WATCH is used to provide a check-and-set(CAS) behavior to Redis transactions](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set).\n\nThe `WATCH` command must be sent in the same connection as the transaction. And normally after the `WATCH` command, we also need to send some other commands to get data from Redis before executing the transaction. Take the following check-and-set case as an example:\n\n```\nWATCH key           // watch a key\nval = GET key       // get value of the key\nnew_val = val + 1   // incr the value\nMULTI               // begin the transaction\nSET key new_val     // set value only if the value is NOT modified by others\nEXEC                // try to execute the transaction.\n                    // if val has been modified, the transaction won't be executed.\n```\n\nHowever, with `Transaction` object, you CANNOT get the result of commands until the whole transaction has been finished. Instead, you need to create a `Redis` object from the `Transaction` object. The created `Redis` object shares the connection with `Transaction` object. With this created `Redis` object, you can send `WATCH` command and any other Redis commands to Redis server, and get the result immediately.\n\nLet's see how to implement the above example with *redis-plus-plus*:\n\n```C++\nauto redis = Redis(\"tcp://127.0.0.1\");\n\n// Create a transaction.\nauto tx = redis.transaction();\n\n// Create a Redis object from the Transaction object. Both objects share the same connection.\nauto r = tx.redis();\n\n// If the watched key has been modified by other clients, the transaction might fail.\n// So we need to retry the transaction in a loop.\nwhile (true) {\n    try {\n        // Watch a key.\n        r.watch(\"key\");\n\n        // Get the old value.\n        auto val = r.get(\"key\");\n        auto num = 0;\n        if (val) {\n            num = std::stoi(*val);\n        } // else use default value, i.e. 0.\n\n        // Incr value.\n        ++num;\n\n        // Execute the transaction.\n        auto replies = tx.set(\"key\", std::to_string(num)).exec();\n\n        // Transaction has been executed successfully. Check the result and break.\n\n        assert(replies.size() == 1 \u0026\u0026 replies.get\u003cbool\u003e(0) == true);\n\n        break;\n    } catch (const WatchError \u0026err) {\n        // Key has been modified by other clients, retry.\n        continue;\n    } catch (const Error \u0026err) {\n        // Something bad happens, and the Transaction object is no longer valid.\n        throw;\n    }\n}\n```\n\n**NOTE**: in the example above, we create `Transaction` object outside the while loop, in order to avoid creating new connection again and again.\n\n#### Create Transaction Without Creating New Connection\n\n**NOTE**: YOU MUST CAREFULLY READ ALL WORDS AND THE VERY IMPORTANT NOTES LINK IN THIS SECTION BEFORE USING THIS FEATURE!!!\n\nIn fact, you can also create a `transaction` object with a connection from the underlying connection pool, so that calling `Redis::transaction` method can be much cheaper (since it doesn't need to create a new connection).\n\nThe prototype of `Redis::transaction` is as follows: `Transaction transaction(bool piped = false, bool new_connection = true);`. If `new_connection` is false, the `Transaction` object will be created with a connection from the underlying pool.\n\n```c++\nConnectionOptions connection_options;\nConnectionPoolOptions pool_options;\n\nRedis redis(connection_options, pool_options);\n\n// Create a Transaction without creating a new connection.\nauto tx = redis.transaction(false, false);\n```\n\nHowever, in this case, you MUST be very careful, otherwise, you might get bad performance or even dead lock. Please carefully check the similar pipeline's [VERY IMPORTANT NOTES section](#very-important-notes), before you use it!\n\nBesides those very important notes, there's another important note for `Transaction`:\n\n- Limit the scope of `Redis` object created by `Transaction::Redis`, i.e. destroy it ASAP.\n\nCheck the following example:\n\n```C++\nauto redis = Redis(opts, pool_opts);\n\n// Create a `Transaction` object without creating a new connection.\nauto tx = redis.Transaction(false, false);\n\n// Create a `Redis`, and this `Redis` object shares the same connection with the `Transaction` object.\nauto r = tx.redis();\n\n// Other code here...\n\n// Execute the transaction.\nauto replies = tx.set(\"key\", \"val\").exec();\n\n// Although `Transaction::exec` has been called, the connection has not been returned to pool.\n// Because the `Redis` object, i.e. `r`, still holds the connection.\n```\n\nSo the above watch example should be modified as follows:\n\n```C++\nauto redis = Redis(opts, pool_opts);\n\n// If the watched key has been modified by other clients, the transaction might fail.\n// So we need to retry the transaction in a loop.\nwhile (true) {\n    try {\n        // Create a transaction without creating a new connection.\n        auto tx = redis.transaction(false, false);\n\n        // Create a Redis object from the Transaction object. Both objects share the same connection.\n        auto r = tx.redis();\n\n        // Watch a key.\n        r.watch(\"key\");\n\n        // Get the old value.\n        auto val = r.get(\"key\");\n        auto num = 0;\n        if (val) {\n            num = std::stoi(*val);\n        } // else use default value, i.e. 0.\n\n        // Incr value.\n        ++num;\n\n        // Execute the transaction.\n        auto replies = tx.set(\"key\", std::to_string(num)).exec();\n\n        // Transaction has been executed successfully. Check the result and break.\n\n        assert(replies.size() == 1 \u0026\u0026 replies.get\u003cbool\u003e(0) == true);\n\n        break;\n    } catch (const WatchError \u0026err) {\n        // Key has been modified by other clients, retry.\n        continue;\n    } catch (const Error \u0026err) {\n        // Something bad happens, and the Transaction object is no longer valid.\n        throw;\n    }\n}\n```\n\n**NOTE**: The difference is that we create the `Transaction` object in the while loop (it's cheap, since it doesn't need to create a new connection). When the `Transaction` object and the `Redis` object created by `Transaction::redis` have been destroyed, the connection will be return to pool.\n\n### Redis Cluster\n\n*redis-plus-plus* supports [Redis Cluster](https://redis.io/topics/cluster-tutorial). You can use `RedisCluster` class to send commands to Redis Cluster. It has similar interfaces as `Redis` class.\n\n#### Connection\n\nBy default, `RedisCluster` connects to all master nodes in the cluster. For each master node, it maintains a connection pool. If you want to read from slave nodes, you need to explicitly set an option (see [below](#read-from-replica) for reference).\n\nYou can initialize a `RedisCluster` instance with `ConnectionOptions` and `ConnectionPoolOptions`. You only need to set one master node's host \u0026 port in `ConnectionOptions`, and `RedisCluster` will get other nodes' info automatically (with the *CLUSTER SLOTS* command). For each master node, it creates a connection pool with the specified `ConnectionPoolOptions`. If `ConnectionPoolOptions` is not specified, `RedisCluster` maintains a single connection to every master node.\n\n```C++\n// Set a master node's host \u0026 port.\nConnectionOptions connection_options;\nconnection_options.host = \"127.0.0.1\";  // Required.\nconnection_options.port = 7000; // Optional. The default port is 6379.\nconnection_options.password = \"auth\"; // Optional. No password by default.\n\n// Automatically get other nodes' info,\n// and connect to every master node with a single connection.\nRedisCluster cluster1(connection_options);\n\nConnectionPoolOptions pool_options;\npool_options.size = 3;\n\n// For each master node, maintains a connection pool of size 3.\nRedisCluster cluster2(connection_options, pool_options);\n```\n\nYou can also specify connection option with an URI. However, in this way, you can only use default `ConnectionPoolOptions`, i.e. pool of size 1, and CANNOT specify password.\n\n```C++\n// Specify a master node's host \u0026 port.\nRedisCluster cluster3(\"tcp://127.0.0.1:7000\");\n\n// Use default port, i.e. 6379.\nRedisCluster cluster4(\"tcp://127.0.0.1\");\n```\n\n##### Read From Replica\n\nIf you want to scale read by reading (possible stale) data from slave nodes, you can specifiy `Role::SLAVE` as the third parameter of `RedisCluster`'s constructor. In this case, *redis-plus-plus* will randomly pick a replica node for each master node of the cluster, and create a connection pool for the replica node.\n\n```C++\nRedisCluster cluster(connection_options, pool_options, Role::SLAVE);\n\nauto val = cluster.get(\"key\");\n```\n\nIn this case, you can only send readonly commands to Redis Cluster. If you try to send a write command, e.g. `set`, `hset`, *redis-plus-plus* will throw an exception. Currently, *redis-plus-plus* doesn't handle this case, i.e. sending write command in `Role::SLAVE` mode, elegantly, and you might get some performance problem. So, NEVER send write command in `Role::SLAVE` mode. I'll fix this issue in the future.\n\n**NOTE**: In `Role::SLAVE` mode, you don't need to manually send [READONLY](https://redis.io/commands/readonly) command to slave nodes. Instead, *redis-plus-plus* will send *READONLY* command to slave nodes automatically.\n\n##### Note\n\n- `RedisCluster` only works with tcp connection. It CANNOT connect to Unix Domain Socket. If you specify Unix Domain Socket in `ConnectionOptions`, it throws an exception.\n- All nodes in the cluster should have the same password.\n- Since [Redis Cluster does NOT support multiple databses](https://redis.io/topics/cluster-spec#implemented-subset), `ConnectionOptions::db` is ignored.\n\n#### Interfaces\n\nAs we mentioned above, `RedisCluster`'s interfaces are similar to `Redis`. It supports most of `Redis`' interfaces, including the [generic command interface](#generic-command-interface) (see `Redis`' [API Reference section](#api-reference) for details), except the following:\n\n- Not support commands without key as argument, e.g. `PING`, `INFO`.\n- Not support Lua script without key parameters.\n\nSince there's no key parameter, `RedisCluster` has no idea on to which node these commands should be sent. However there're 2 workarounds for this problem:\n\n- If you want to send these commands to a specific node, you can create a `Redis` object with that node's host and port, and use the `Redis` object to do the work.\n- Instead of host and port, you can also call `Redis RedisCluster::redis(const StringView \u0026hash_tag)` to create a `Redis` object with a hash-tag specifying the node. In this case, the returned `Redis` object creates a new connection to Redis server. **NOTE**: the returned `Redis` object, **IS NOT THREAD SAFE!**. Also, when using the returned `Redis` object, if it throws exception, you need to destroy it, and create a new one with the `RedisCluster::redis` method.\n\nAlso you can use the [hash tags](https://redis.io/topics/cluster-spec#keys-hash-tags) to send multiple-key commands.\n\nSee the [example section](#examples-2) for details.\n\n##### Publish/Subscribe\n\nYou can publish and subscribe messages with `RedisCluster`. The interfaces are exactly the same as `Redis`, i.e. use `RedisCluster::publish` to publish messages, and use `RedisCluster::subscriber` to create a subscriber to consume messages. See [Publish/Subscribe section](#publishsubscribe) for details.\n\n##### Pipeline and Transaction\n\nYou can also create `Pipeline` and `Transaction` objects with `RedisCluster`, but the interfa","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsewenew%2Fredis-plus-plus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsewenew%2Fredis-plus-plus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsewenew%2Fredis-plus-plus/lists"}