---
title: C++ SDK
description: "C++ SDK: ask the ten ThinkThen questions from C++17 and get yes, no or not sure back as plain C++ values."
goal: The reader compiles a first decide in C++, catches not sure and failures, and finds the C++ call for each of the ten functions.
reader: A C++ developer who wants to ask questions about text from a program
kind: reference
verified: 2026-11-03
sidebar:
  label: "C++"
---

# C++ SDK

The C++ SDK gives your C++17 program the ten functions as methods on one client; each call gives back a result whose `value()` is a plain C++ value, such as `true` or `false` from decide, and throws when the answer is not sure or failed.

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::decide refund{"Does the customer request a refund?"};
    // value() throws when not sure or failed, so neither reads as false.
    const bool is_refund =
        client.decide(refund, "Please refund my order.").value();
    std::cout << std::boolalpha << is_refund << '\n';
} catch (const thinkthen::unsure&) {
    std::cerr << "not sure: a person should read it\n";
    return 1;
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
true
```


`true` means yes: the message asks for a refund.

It needs a C++17 compiler. Extract the C++ and C archives for your platform from the [0.2.0 release](https://github.com/botassembly/thinkthen/releases/tag/v0.2.0) into one `thinkthen` folder, save the program as `example.cpp`, then compile it and run `./example`:

```sh
c++ -std=c++17 -Wall -Wextra -Werror example.cpp -o example \
  $(PKG_CONFIG_PATH="$PWD/thinkthen/lib/pkgconfig" \
    pkg-config --cflags --libs thinkthen-c) \
  -Wl,-rpath,"$PWD/thinkthen/lib"
```

With CMake, use `find_package(ThinkThenCpp CONFIG REQUIRED)` and link `ThinkThen::Cpp`. The client uses the key from the [Quickstart](/quickstart/).

## Catch not sure and failures

Set a low bar and a high bar with `thinkthen::threshold::band`. Between them, decide is not sure, and `value()` throws `thinkthen::unsure`. When ThinkThen can't judge the message, `value()` throws `thinkthen::error` instead. Neither one can pass for `false`.

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    // No below 0.2, yes at 0.8 or above, not sure between.
    const auto refund =
        thinkthen::decide{"Does the customer request a refund?"}
            .threshold(thinkthen::threshold::band(0.2, 0.8));
    const auto answer =
        client.decide(refund, "I was charged twice for one invoice.");
    // value() throws thinkthen::unsure here, so it never reads as false.
    std::cout << std::boolalpha << answer.value() << '\n';
} catch (const thinkthen::unsure& not_sure) {
    std::cout << "not sure: ask a person";
    if (const auto probability = not_sure.probability())
        std::cout << " (probability " << *probability << ')';
    std::cout << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what()
              << '\n';
    return 1;
}
```

Output

```text
not sure: ask a person (probability 0.7)
```


A double charge might be a refund request, or might not, so the answer is not sure. Send it to a person.

`thinkthen::unsure` derives from `std::exception`, not from `thinkthen::error`, so a handler for errors never catches a not-sure answer. Its `probability()` is the chance of yes, when the model gave one.

`thinkthen::error` derives from `std::system_error`. `code().message()` names the error, such as `record.empty`, and `what()` says what went wrong; [Troubleshooting](/reference/troubleshooting/) lists each one. Blank text throws `thinkthen::error` from the call itself, before any request is sent. A refused question throws it when you build the question.

To check the outcome without an exception, test `unsure()` or `failed()`, or call `answer()`, which gives a pointer that is null when there is no answer. A decide or filter result also tests as its answer in an `if`, and throws the same way.

## The ten functions

Each function reads the same support-inbox messages as the function pages. Each program is a whole file that compiles with the line above.

### decide

<details class="example-disclosure">
<summary>Yes or no about one message</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::decide refund{"Does the customer request a refund?"};
    // value() throws when not sure or failed, so neither reads as false.
    const bool is_refund =
        client.decide(refund, "Please refund my order.").value();
    std::cout << std::boolalpha << is_refund << '\n';
} catch (const thinkthen::unsure&) {
    std::cerr << "not sure: a person should read it\n";
    return 1;
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
true
```


Yes: the message asks for a refund. A `thinkthen::decide` question takes `.threshold(0.7)` for one bar, `.threshold(thinkthen::threshold::band(0.2, 0.8))` for a low and a high bar, and `.yes_means(...)` and `.no_means(...)`. `client.decide_each` takes many messages and gives one result each, in order. [decide](/functions/decide/) shows every option.

</details>

### choose

<details class="example-disclosure">
<summary>The one choice that fits</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::choose about{"What is the message about?",
                                  {"billing", "shipping", "other"}};
    const std::string topic =
        client.choose(about, "Please refund my order.").value();
    std::cout << topic << '\n';
} catch (const thinkthen::unsure&) {
    std::cerr << "not sure: a person should read it\n";
    return 1;
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
billing
```


A refund is about billing. A `thinkthen::choose` question takes `.descriptions(...)`, a map from each choice to what it means, and `.threshold(...)`, which can make choose not sure. [choose](/functions/choose/) shows every option.

</details>

### filter

<details class="example-disclosure">
<summary>Only the messages that answer yes</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::filter refund{"Does the customer request a refund?"};
    const std::vector<std::string> messages{
        "Please refund my order.",
        "Where is my parcel?",
        "I was charged twice for one invoice.",
    };
    // Only the messages that answer yes. Failed messages are reported
    // together after the last kept one.
    for (const auto& refund_request : client.filter_each(refund, messages))
        std::cout << std::get<thinkthen::owned_text>(refund_request).value
                  << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    for (const auto& failed : failure.failed_rows())
        std::cerr << "message " << failed.position << ": " << failed.message
                  << '\n';
    return 1;
}
```

Output

```text
Please refund my order.
I was charged twice for one invoice.
```


The parcel question is left out. `filter_each` gives back your own messages, each as the `thinkthen::owned_record` you passed in, and leaves out the ones it is not sure about. A failed message does not stop it: it gives back every kept message, then throws one `thinkthen::error` whose `failed_rows()` lists each failed message with its `position`, `code` and `message`. `filter_rows_each` gives every message with its outcome instead, where `kept()` is true only for a yes. [filter](/functions/filter/) shows every option.

</details>

### tag

<details class="example-disclosure">
<summary>Every label that fits</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::tag appear{"Which topics appear?",
                                {"billing", "shipping"}};
    // Every label that fits, possibly none.
    const std::vector<std::string> topics =
        client.tag(appear, "I was charged twice and my parcel is late again.")
            .value();
    for (const auto& topic : topics) std::cout << topic << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
billing
shipping
```


The message is about both billing and shipping. The answer is a `std::vector<std::string>`, empty when no label fits. [tag](/functions/tag/) shows every option.

</details>

### score

<details class="example-disclosure">
<summary>A number from 0 to 1 on your scale</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::score how_urgent{"How urgent is the request?",
                                      {"routine", "urgent"}};
    // 0 is routine, 1 is urgent.
    const double urgency =
        client.score(how_urgent, "Please refund my order.").value();
    std::cout << urgency << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
0.05
```


The levels run lowest first, so a plain refund request scores near routine. [score](/functions/score/) shows every option.

</details>

### rank

<details class="example-disclosure">
<summary>Every message, highest first</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::rank by_urgency{"How urgent is the request?",
                                     {"routine", "urgent"}};
    const std::vector<std::string> messages{
        "Please refund my order.",
        "Where is my parcel?",
        "I was charged twice for one invoice.",
    };
    // Rank 1 is the most urgent. item() is the message that rank belongs to.
    for (const auto& ranked : client.rank_each(by_urgency, messages))
        std::cout << ranked.value() << ' '
                  << std::get<thinkthen::owned_text>(ranked.item()).value
                  << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
1 I was charged twice for one invoice.
2 Where is my parcel?
3 Please refund my order.
```


The double charge is the most urgent. `value()` is the rank, starting at 1, and `item()` is the message you passed in. A `thinkthen::rank` question takes `.top(n)` to keep only the first few. [rank](/functions/rank/) shows every option.

</details>

### find

<details class="example-disclosure">
<summary>The line that answers the question</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::find requested{"Which line states the requested action?"};
    const std::optional<thinkthen::span> action =
        client
            .find(requested,
                  "Morgan works for Example Group.\nPlease refund my order.")
            .value();
    // Empty when no line answers the question.
    if (action) std::cout << action->text << '\n';
    else std::cout << "no line answers\n";
} catch (const thinkthen::unsure&) {
    std::cerr << "not sure: a person should read it\n";
    return 1;
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
Please refund my order.
```


The second line states the action. The answer is a `std::optional<thinkthen::span>`, with the line's `text` and its offsets in UTF-8 bytes, and it is empty when no line answers. [find](/functions/find/) shows every option.

</details>

### recognize

<details class="example-disclosure">
<summary>Each name in the text and its kind</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::recognize people_and_groups{
        std::vector<std::string>{"person", "organization"}};
    const std::vector<thinkthen::name> names =
        client.recognize(people_and_groups, "Morgan works for Example Group.")
            .value();
    for (const auto& name : names)
        std::cout << name.kind << ": " << name.text << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
person: Morgan
organization: Example Group
```


Each `thinkthen::name` holds the name's `text`, its `kind` and its offsets in UTF-8 bytes. [recognize](/functions/recognize/) shows every option.

</details>

### relate

<details class="example-disclosure">
<summary>Names linked by the relations you allow</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::relate works_for{
        std::vector<thinkthen::rule>{{"works_for", "person", "organization"}}};
    const std::vector<thinkthen::relation> relations =
        client
            .relate(works_for,
                    "Morgan works for Example Group.\nPlease refund my order.")
            .value();
    for (const auto& relation : relations)
        std::cout << relation.source.text << " --" << relation.label << "--> "
                  << relation.target.text << '\n';
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
Morgan --works_for--> Example Group
```


A `thinkthen::rule` names the relation, then the kind of name at each end. It also takes a description and whether the relation runs both ways. [relate](/functions/relate/) shows every option.

</details>

### annotate

<details class="example-disclosure">
<summary>Several questions for each message</summary>

```cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>

int main() try {
    thinkthen::client client;
    const thinkthen::annotate triage_form{thinkthen::annotation_questions{
        {"refund", thinkthen::decide{"Does the customer request a refund?"}},
        {"topic", thinkthen::choose{"What is the message about?",
                                    {"billing", "shipping", "other"}}},
        {"urgency", thinkthen::score{"How urgent is the request?",
                                     {"routine", "urgent"}}},
    }};
    const std::vector<std::string> messages{
        "Please refund my order.",
        "Where is my parcel?",
        "I was charged twice for one invoice.",
    };
    for (const auto& triage : client.annotate_each(triage_form, messages)) {
        // Each answer prints bare, or as its not-sure or failed status.
        const thinkthen::annotations& answers = triage.value();
        std::cout << thinkthen::get<thinkthen::decide>(answers, "refund")
                  << ' ' << thinkthen::get<thinkthen::choose>(answers, "topic")
                  << ' '
                  << thinkthen::get<thinkthen::score>(answers, "urgency")
                  << '\n';
    }
} catch (const thinkthen::error& failure) {
    std::cerr << failure.code().message() << ": " << failure.what() << '\n';
    return 1;
}
```

Output

```text
true billing 0.05
false shipping 0.14
true billing 0.45
```


`thinkthen::get<thinkthen::decide>(answers, "refund")` reads one answer by name, as the question type it was built with. Each answer has its own `value()`, which throws when not sure, as decide does. Printed directly, it shows `unsure` or `failed: CODE` instead of an answer. For one message, call `client.annotate(question, message)`. [annotate](/functions/annotate/) shows every option.

</details>

## The full API

Every question takes `.field("/text")` to judge one field of a JSON message, passed as `thinkthen::json_text`. Each `_each` call also takes a `thinkthen::record_source`, a function that returns the next message or `std::nullopt` after the last. A stream is read once; leaving the loop or calling `close()` stops it. The headers in [`include/thinkthen/`](https://github.com/botassembly/thinkthen/tree/v0.2.0/sdk/cpp/include/thinkthen) declare every type and method.
