So, before I get too far into the challenges, I do need to specify which C++ version I’m using, and which compiler. I’m using C++20, because by now it’s fairly stable, most of the bugs have been worked out of compilers, and most features are generally available in the big three compilers. That said, there are a lot of fancy features that would make these challenges a lot easier, like C++26 reflection (in theory anyway)1. Also, all of these tests are going to be type focused, not runtime or memory safety focused. Which means, if I don’t show a proper destructor rule or correctly do a Rule of 5 in a C++ solution, then that’s okay. It passes the type checks, and this is a type challenge.
Second, the compiler/platform I’m using is GCC 16 on Linux. I’m not on Windows or Mac, and in C++ world that does matter. If my solution doesn’t work for you, then spin up a VM or use godbolt to try it out with Linux GCC.
With that out of the way, let’s get into our first actual challenge. It’s labelled “easy” - which is very true for TypeScript.
Also, the C++ versions of the challenges are now posted online!
TypeScript Challenge: Pick
The TypeScript challenge is pretty straightforward. We implement the Pick<> type that comes into the language. This type is given an interface and a list of types, and then becomes a new interface of just the selected fields in the provided interface. Other fields are discarded. Here’s the code for the challenge:
// What we change
type MyPick<T, K> = any
// Tests
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
// @ts-expect-error
MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]
interface Todo {
title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
}
interface Expected2 {
title: string
completed: boolean
}Nothing too crazy. The solution is pretty straightforward too. We need to restrict K to be a key of T, and then we need to iterate over every key of T, see if it’s in K, and if so keep it - otherwise we discard it. The solution is as follows:
type MyPick<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? Key : never]: T[Key]
}The K extends keyof T is our first step - restricting K to a key of T. Next, we declare a new type variable Key, and we use Key in keyof T to iterate over every key in T. We then need to do something with each key, which is where the as keyword comes it. It basically lets us declare a list comprehension for our types. We then have a ternary (basically an if statement) where if Key extends K (basically our Key matches one of our input fields to pick), then we return that Key. Otherwise, we return never - which causes TypeScript to filter it out since never is not a valid key type.
We then have the right-hand side of the expression, T[Key], which basically says “copy the type of each key from the original interface.” And with that, we’re done with the TypeScript challenge!
C++ Challenge
Here’s the challenge ported over to C++20 - with an important change. C++20 doesn’t have the ability to iterate fields and keys the same way - that’s coming in C++26. So, for now, we will have an enum of fields that can be picked, we will pass in that enum, and we will only pick fields in that list of enums.
#include <concepts>
#include <type_traits>
#include <string>
enum class PickFields {
TITLE, DESCRIPTION, COMPLETED,
};
// What we change
template<class RefType, PickFields ... Fields>
struct Pick;
// Tests
#include "../utilities/type_checks.h"
template<class T, class U>
concept HasTitle = requires(T t, U u)
{
{
HoldsTrue<std::is_same_v<decltype(std::declval<T>().title), decltype(std::declval<U>().title)> >{}
} -> std::same_as<std::true_type>;
};
template<class T, class U>
concept HasDescription = requires(T t)
{
{
HoldsTrue<std::is_same_v<decltype(std::declval<T>().description), decltype(std::declval<U>().description)> >
{}
} -> std::same_as<std::true_type>;
};
template<class T, class U>
concept HasCompleted = requires(T t)
{
{
HoldsTrue<std::is_same_v<decltype(std::declval<T>().completed), decltype(std::declval<U>().completed)> >{}
} -> std::same_as<std::true_type>;
};
struct Todo {
std::string title;
std::string description;
bool completed;
};
struct Assignment {
std::string_view title;
std::string_view description;
int completed;
};
using TestType1 = Pick<Todo, PickFields::TITLE, PickFields::DESCRIPTION>;
static_assert(HasTitle<TestType1, Todo>);
static_assert(HasDescription<TestType1, Todo>);
static_assert(!HasCompleted<TestType1, Todo>);
using TestType2 = Pick<Todo, PickFields::DESCRIPTION, PickFields::COMPLETED>;
static_assert(!HasTitle<TestType2, Todo>);
static_assert(HasDescription<TestType2, Todo>);
static_assert(HasCompleted<TestType2, Todo>);
using TestType3 = Pick<Assignment, PickFields::DESCRIPTION, PickFields::COMPLETED>;
static_assert(!HasTitle<TestType3, Assignment>);
static_assert(HasDescription<TestType3, Assignment>);
static_assert(HasCompleted<TestType3, Assignment>);
using TestType4 = Pick<Assignment, PickFields::TITLE>;
static_assert(HasTitle<TestType4, Assignment>);
static_assert(!HasDescription<TestType4, Assignment>);
static_assert(!HasCompleted<TestType4, Assignment>);The testing code is a lot more verbose than in TypeScript. But, it’s running most of the same checks, and a few extras. We do have a separate concept per field since C++ defaults to “concrete” structs while TypeScript defaults to “ephemeral” interfaces. Basically, we can’t make a struct to compare against in C++ with std::is_same since the comparison struct would have it’s own type id - which then causes the answer and comparison structs to be non-identical. So, we have to check properties one-by-one instead.
In contrast, TypeScript interfaces don’t have a type id or concrete backing; anything with that shape matches - including other interfaces. So instead of checking type properties individually, TypeScript just checks the entire shape all at once. Pretty nifty.
There are some helper types defined in a common header above. For completeness in this post, those type are provided below. But, for future posts, you’ll need to download the repository to run the tests.
C++ Solution
The solution to the C++ challenge is more verbose than the TypeScript one, but it is (in some ways) easier to grasp since the iteration is explicitly done via recursion. Here it is:
template<class RefType, PickFields ...Fields>
struct Pick;
// Recursion base case - no fields = empty string
template<class RefType>
struct Pick<RefType> {};
// When we get a title enum, grab the title field and keep looping
template<class RefType, PickFields ...Rest>
struct Pick<RefType, PickFields::TITLE, Rest...> : public Pick<RefType, Rest...> {
decltype(std::declval<RefType>().title) title;
};
// When we get a description enum, grab the description field and keep looping
template<class RefType, PickFields ...Rest>
struct Pick<RefType, PickFields::DESCRIPTION, Rest...> : public Pick<RefType, Rest...> {
decltype(std::declval<RefType>().description) description;
};
// When we get the completed enum, grab the completed field and keep looping
template<class RefType, PickFields ...Rest>
struct Pick<RefType, PickFields::COMPLETED, Rest...> : public Pick<RefType, Rest...> {
decltype(std::declval<RefType>().completed) completed;
};The main tricks here are 1. variadic templates - basically templates that can take one or more parameters - and 2. enums can be passed into templates (or, basically any type that is implicitly convertible to an integer type). This allows us to pass in the enum list in the first place.
Next, we have actually branching on the fields to pick. We don’t match everything at once, instead, we match one element and resolve that one element. We then inherit from Pick with the rest of the enum fields. This inheritance is how we “compose” our fields together.
To copy the value of our fields, we first get a declaration value that we can do a field access on with std::declval. This, unfortunately, gives us a field and not a value. So, we have to wrap it with decltype to get an actual type. And with that, we now have our Pick!
C++23 doesn’t have a lot that I feel like would help me in this particular challenge. It does have some niceties like std::expected, std::byteswap, and std::unreachable - but they don’t really help for something focused on types.

