Examples

Each example is taken from the test suite for Tolc and, given that you use the latest version, you can expect them all to work.

Each C++ library named MyLib exports their functions and objects with a prefix of MyLib. In every test the library name is simply m for brevity. The examples that follow contains a bit of C++ code, and the respective Objective-C and Swift code using it. Each Objective-C example is wrapped in the following boilerplate that is removed to make the examples more readable:

#include <m.h>

int main() {
  @autoreleasepool {
    assert([m sayHello] == "Hello");
  }
}

And the same for Swift:

import m_swift

assert(m.sayHello() == "Hello")

Classes


class WithConstructor {
public:
  explicit WithConstructor() : m_v(10) {}
  explicit WithConstructor(int v) : m_v(v) {}

  int getV() { return m_v; }
private:
  int m_v;
};

class WithFunction {
public:
  int add(int i, int j) {
    return i + j;
  }
};

class WithStatic {
public:
  static double getPi() {
    return 3.14;
  }

  static int const answer = 42;
};

class WithMember {
public:
  explicit WithMember() : i(10), phi(1.618) {}

  int i;
  double const phi;
};


// Constructors are overloaded with their argument types
mWithConstructor* ten = [[mWithConstructor alloc] init];
assert([ten getV] == 10);

mWithConstructor* five = [[mWithConstructor alloc] initWithInt:5];
assert([five getV] == 5);

// Member functions are available after construction
mWithFunction* withFunction = [[mWithFunction alloc] init];
assert([withFunction add: 2 j: 5] == 7);

// Static functions can be called
// without instantiating the class
assert([mWithStatic getPi] == 3.14);
// You can access static variables
// without instantiating the class
assert([mWithStatic answer] == 42);

// Member variables
mWithMember* member = [[mWithMember alloc] init];
assert(member.i == 10);
// i is not marked const
member.i = 5;
assert(member.i == 5);

// phi is marked const
// Cannot be assigned
assert(member.phi == 1.618);

Enums


enum Unscoped {
    Under,
    Uboat,
};

enum class Scoped {
    Sacred,
    Snail,
};

class EnumTest {
public:
    explicit EnumTest(Scoped _s) : s(_s) {};

    Unscoped f(Unscoped u) {
        return u;
    }

    Scoped s;
};

namespace NS {
    // Documentation describing the enum
    enum class Deep {
        Double,
        Down,
    };
}



// C++11 enums work
mEnumTest* enumTest = [[mEnumTest alloc] initWithScoped:mScopedSnail];
mScoped snail = mScopedSnail;
assert(enumTest.s == snail);

// Aswell as legacy enums
mUnscoped uboat = mUnscopedUboat;
assert([enumTest f:uboat] == uboat);

// Enums under namespaces are available
// under the corresponding submodule
/* deep = m.NS.Deep.Down */
/* assert(deep != m.NS.Deep.Double) */

// Documentation carries over from C++
// self.assertIn("Documentation describing the enum", m.NS.Deep.__doc__)

Functions


#include <filesystem>
#include <string>

int meaningOfLife() {
    return 42;
}

std::string sayHello(std::string const& name) {
    return "Hello " + name;
}

std::filesystem::path getPath() {
    return std::filesystem::path("/path/to/stuff.hpp");
}

namespace Inner {
    double pi() {
        return 3.14;
    }
}


// Global functions gets added to
// a purely static class with
// the name of the library
assert([m meaningOfLife] == 42);

// Strings can be used
assert([[m sayHello:@"Tolc"] isEqualToString:@"Hello Tolc"]);

// Aswell as filesystem paths
assert([[m getPath] isEqualToString:@"/path/to/stuff.hpp"]);

// Functions within namespaces
// are available with the
// namespaces names merged
assert([mInner pi] == 3.14);

Global Variables


#include <string>
#include <string_view>

static int i = 0;
namespace Nested {
    int life = 42;
    std::string s = "Hello World";
    constexpr std::string_view constant = "A constant";
}


// Starts at 0 and can be changed
assert(m.i == 0);
m.i = 5;
assert(m.i == 5);

// Nested with the same name
assert(mNested.life == 42);

// Strings also work
assert([mNested.s isEqualToString:@"Hello World"]);

// And string_view
assert([mNested.constant isEqualToString:@"A constant"]);

Member Variables


#include <string>

class SimpleMember {
public:
    explicit SimpleMember() : myString("Hello") {}

    std::string myString;
};

class ConstMember {
public:
    const int i = 42;
};

class PrivateMember {
public:
    explicit PrivateMember(std::string s) : myString(s) {}

private:
    std::string myString;
};

namespace MyLib {

    class Nested {
    public:
        double d = 4.3;
    };
}


// Mutable member variables can be changed
mSimpleMember* simpleMember = [[mSimpleMember alloc] init];
assert([simpleMember.myString isEqualToString:@"Hello"]);
simpleMember.myString = @"Changed now!";
assert([simpleMember.myString isEqualToString:@"Changed now!"]);

mConstMember* constMember = [[mConstMember alloc] init];
assert(constMember.i == 42);

mMyLibNested* nested = [[mMyLibNested alloc] init];
assert(nested.d == 4.3);

Namespaces


#include <string>

/*
* MyLib contains a bunch of MyLib functions
*/
namespace MyLib {

int complexFunction() {
    return 5;
}

    namespace We {
        namespace Are {
            namespace Going {
                namespace Pretty {
                    namespace Deep {
                        std::string meaningOfLife() {
                            return "42";
                        }
                    }
                }
            }
        }
    }
}



// Namespaces corresponds to classes
// with {library name} + join(namespaces)
// where functions are static class functions
assert([mMyLib complexFunction] == 5);

// You can nest namespaces arbitrarily deep
NSString* lifeProTips = [mMyLibWeAreGoingPrettyDeep meaningOfLife];
assert([lifeProTips isEqualToString:@"42"]);

Overloaded Functions


#include <string>

// Overloaded free functions
std::string sayHello() {
    return "Hello!";
}

std::string sayHello(std::string to) {
    return std::string("Hello ") + to;
}

std::string sayHello(size_t times) {
    std::string greeting = "";
    for (size_t i = 0; i < times; ++i) {
        greeting += "Hello!";
    }
    return greeting;
}

class Overload {
public:
    // Overloaded constructor
    Overload() : m_s() {};
    Overload(std::string s) : m_s(s) {};

    // Overloaded class functions
    std::string getStuff() { return "Stuff"; }
    std::string getStuff(std::string customStuff) { return customStuff; }

private:
    std::string m_s;
};


// Overloaded functions work the same as in C++
// Free function overload
assert([[m sayHello] isEqualToString:@"Hello!"]);
assert([[m sayHelloString:@"Tolc"] isEqualToString:@"Hello Tolc"]);
assert([[m sayHelloUnsignedLongInt:2] isEqualToString:@"Hello!Hello!"]);

// Class constructor overload
mOverload* overload = [[mOverload alloc] init];
mOverload* overloadWithString = [[mOverload alloc] initWithString:@"Overloaded!"];

// Class function overload
assert([[overload getStuff] isEqualToString:@"Stuff"]);
assert([[overload getStuffString:@"Other"] isEqualToString:@"Other"]);

Passing classes between languages


#include <string>

class MyClass {
public:
    explicit MyClass(std::string s) : m_s(s) {}

    std::string* getS() { return &m_s; }

private:
    std::string m_s;
};

MyClass buildMyClass(std::string const& s) {
    return MyClass(s);
}

class Owner {
public:
    explicit Owner(MyClass m) : m_myClass(m) {};

    MyClass getMyClass() const { return m_myClass; }

private:
    MyClass m_myClass;
};

struct Point2d {
    int x;
    int y;
};

Point2d getMiddle(std::pair<Point2d, Point2d> p) {
    return {(p.first.x + p.second.x) / 2, (p.first.y + p.second.y) / 2};
}


NSString* phrase = @"Hello from Objective-C";
mMyClass* myClass = [m buildMyClass:phrase];
assert([[myClass getS] isEqualToString:phrase]);

// Passing Objective-C classes to C++ classes
mOwner* owner = [[mOwner alloc] initWithMyClass:myClass];
assert([[[owner getMyClass] getS] isEqualToString:phrase]);

// Container of user defined classes
mPoint2d* a = [[mPoint2d alloc] init];
a.x = 1;
a.y = 0;
mPoint2d* b = [[mPoint2d alloc] init];
b.x = 3;
b.y = 0;

NSArray* points = [NSArray arrayWithObjects:a, b, nil];
mPoint2d* middle = [m getMiddle:points];
assert(middle.x == 2);
assert(middle.y == 0);

Smart Pointers


#include <memory>

struct Data {
  int i = 5;
};

struct SharedData {
  int i = 10;
};

std::unique_ptr<Data> createData() {
  return std::make_unique<Data>();
}

// This moves the data,
// destroying it at the end
// Same as C++
int consumeData(std::unique_ptr<Data> data) {
  return data->i + 20;
}

std::shared_ptr<SharedData> createSharedData() {
  return std::make_shared<SharedData>();
}

// Does not move the data
// The pointer is valid after the function call
int consumeSharedData(std::shared_ptr<SharedData> data) {
  return data->i + 20;
}


// std::unique_ptr acts as a normal value
mData* data = [m createData];
assert(data.i == 5);

// This moves the data,
// destroying it at the end
// Same as C++
assert([m consumeData:data] == 25);

// Any access now results
// in undefined behaviour
// (possibly a crash)
// NSLog(@"%i", data.i);

// std::shared_ptr acts as a normal value
// But all mSharedData have their internal
// classes handled by a std::shared_ptr
mSharedData* sharedData = [m createSharedData];
assert(sharedData.i == 10);

// This copies the smart pointer,
// incrementing its counter.
// Valid to use sharedData after this call.
assert([m consumeSharedData:sharedData] == 30);

// No crash
NSLog(@"%i", sharedData.i);

Templates


#include <array>
#include <map>
#include <string>
#include <vector>

template <typename T>
T getSomething(T something) {
  return something;
}

template std::string getSomething(std::string something);
template int getSomething(int);
template std::vector<std::string> getSomething(std::vector<std::string>);

template <typename T>
class MyClass {
public:
T myFun(T type) {
    return type;
}
};

MyClass<char> getMyClass(MyClass<char> c) {
    return c;
}

template class MyClass<int>;
template class MyClass<std::array<int, 3>>;


// getSomething<std::string>
NSString* hi = [m getSomethingString:@"Hi"];
assert([hi isEqualToString:@"Hi"]);

// getSomething<int>
int five = [m getSomethingInt:5];
assert(five == 5);

// getSomething<std::vector<std::string>>
NSArray* v = [m getSomethingVectorString:@[@"Hi"]];
assert([v count] == 1);
assert([[v objectAtIndex:0] isEqualToString:@"Hi"]);

// MyClass<char>
mMyClassChar* myClassChar = [[mMyClassChar alloc] init];;
assert([myClassChar myFun:25] == 25);;
// Still the same after passing through a function
mMyClassChar* passedThrough = [m getMyClass:myClassChar];;
assert([passedThrough myFun:25] == 25);;

// MyClass<int>
mMyClassInt* myClassInt = [[mMyClassInt alloc] init];
assert([myClassInt myFun:25] == 25);

// MyClass<std::array<int, 3>>
mMyClassArrayInt3* myClassArray = [[mMyClassArrayInt3 alloc] init];
NSArray* arr = [myClassArray myFun:@[@(0), @(1), @(2)]];
assert([arr count] == 3);
assert([[arr objectAtIndex:0] intValue] == 0);
assert([[arr objectAtIndex:1] intValue] == 1);
assert([[arr objectAtIndex:2] intValue] == 2);

std::array


#include <algorithm>
#include <array>

std::array<int, 3> const f() {
  return {0, 1, 2};
}

bool allOf(std::array<bool, 3> const& conditions) {
  return std::all_of(
      conditions.begin(), conditions.end(),
      [](auto c) { return c; });
}

double sum(std::array<double, 3> const& numbers) {
  double sum = 0;
  for (double number : numbers) {
    sum += number;
  }
  return sum;
}



// std::array corresponds to NSArray
NSArray* v = [m f];
assert([v count] == 3);

// The array contains {0, 1, 2}
assert([[v objectAtIndex:0] intValue] == 0);
assert([[v objectAtIndex:1] intValue] == 1);
assert([[v objectAtIndex:2] intValue] == 2);

// Sending NSArray into function works as well
NSArray* conditions = @[@(YES), @(YES), @(NO)];
assert([m allOf:conditions] == NO);

NSArray<NSNumber*>* toSum = @[@(1.1), @(2.2), @(3.3)];
assert([m sum:toSum] == 6.6);

// Error handling
@try {
  // Array with the wrong size
  NSArray<NSNumber*>* toSum = @[@(1.1), @(2.2)];
  // Expected size == 3
  [m sum:toSum];
  // Should throw exception before
  assert(NO);
} @catch(NSException* error) {
  assert([[error name] isEqualToString:@"TypeException"]);
  NSString* reason =
    @"The size of the array does not match the expected fixed size. Expected: 3, Got: 2.";
  assert([[error reason] isEqualToString:reason]);
}

std::deque


#include <string>
#include <deque>

std::deque<std::string>
surround(std::deque<std::string> d,
         std::string const& message) {
  d.push_front(message);
  d.push_back(message);
  return d;
}


// std::deque corresponds to NSArray
NSArray* myDeque = @[@"middle"];
NSArray* surroundedDeque =
  [m surround:myDeque message:@"surrounded"];
assert([surroundedDeque count] == 3);

assert([[surroundedDeque objectAtIndex:0]
  isEqualToString:@"surrounded"]);

assert([[surroundedDeque objectAtIndex:1]
  isEqualToString:@"middle"]);

assert([[surroundedDeque objectAtIndex:2]
  isEqualToString:@"surrounded"]);

std::filesystem::path


#include <filesystem>
#include <vector>

std::filesystem::path
takingPath(std::filesystem::path const& p) {
    return p;
}

std::filesystem::path
parent(std::filesystem::path const& p) {
    return p.parent_path();
}

std::filesystem::path
joinPaths(std::vector<std::filesystem::path> arrayToSum) {
    std::filesystem::path sum;
    for (auto f : arrayToSum) {
        sum /= f;
    }
    return sum;
}


// std::filesystem::path corresponds to NSString
NSString* path = @"Hello/my/name/is/Tolc";

// Passing through a function
NSString* result = [m takingPath:path];
assert([result isEqualToString:path]);

NSString* parent = [m parent:path];
assert([parent isEqualToString:@"Hello/my/name/is"]);

NSArray* paths = @[@"to", @"the", @"heart"];
NSString* joined = [m joinPaths:paths];
assert([joined isEqualToString:@"to/the/heart"]);

std::list


#include <string>
#include <list>

std::list<std::string> getList() {
  return {"Linked", "list", "fun"};
}


// std::list corresponds to NSArray
NSArray* words = [m getList];
assert([words count] == 3);

assert([[words objectAtIndex:0] isEqualToString:@"Linked"]);
assert([[words objectAtIndex:1] isEqualToString:@"list"]);
assert([[words objectAtIndex:2] isEqualToString:@"fun"]);

std::map


#include <map>
#include <string>
#include <vector>

std::map<std::string, int> getThings() {
  return {{"Greetings", 5}};
}

std::map<std::string, std::vector<double>> getCities() {
  return {
  {"Stockholm",
    {59.33, 18.06}},
  {"San Francisco",
    {37.77, -122.43}}
  };
}


// std::map translates to a NSDictionary
NSDictionary* dict = [m getThings];
assert([dict count] == 1);
NSNumber* n = [dict objectForKey:@"Greetings"];
assert(n != nil);
assert([n intValue] == 5);

// Nested containers work as well
NSDictionary* cities = [m getCities];
assert([cities count] == 2);
NSArray* stockholm = [cities objectForKey:@"Stockholm"];
assert(stockholm != nil);
assert([stockholm count] == 2);
assert([[stockholm objectAtIndex:0] doubleValue] == 59.33);
assert([[stockholm objectAtIndex:1] doubleValue] == 18.06);

NSArray* sanFrancisco = [cities objectForKey:@"San Francisco"];
assert(sanFrancisco != nil);
assert([sanFrancisco count] == 2);
assert([[sanFrancisco objectAtIndex:0] doubleValue] == 37.77);
assert([[sanFrancisco objectAtIndex:1] doubleValue] == -122.43);

std::optional


#include <optional>
#include <string>

std::string
answer(std::optional<std::string> const& question) {
  if (question) {
    return "Please be more specific.";
  }
  return "That's no question!";
}



// std::optional is either the value or nil
NSString* answer = [m answer:@"How do I take over the world?"];
assert([answer isEqualToString:@"Please be more specific."]);

// nil is the equivalent of std::nullopt on the C++ side
NSString* noAnswer = [m answer:nil];
assert([noAnswer isEqualToString:@"That's no question!"]);

std::pair


#include <string>

class Greeter {
public:
  explicit Greeter(std::pair<std::string, int> greetings)
    : m_greetings(greetings) {}

  std::pair<std::string, int> getGreetings() {
    return m_greetings;
  }

  std::string joinGreetings() {
    std::string joined;
    for (int i = 0; i < m_greetings.second; ++i) {
      joined += m_greetings.first;
    }
    return joined;
  }

private:
  std::pair<std::string, int> m_greetings;
};


// std::pair corresponds to a NSArray
// with two values
NSArray* greetings = [NSArray
  arrayWithObjects:@"Hey ", @(3), nil];
assert([greetings count] == 2);

// Sending a pair to a function
mGreeter* g = [[mGreeter alloc]
  initWithPairStringInt:greetings];

// Joining the greetings 3 times
NSString* joined = [g joinGreetings];
assert([joined isEqualToString:@"Hey Hey Hey "]);

// Error handling
@try {
  // Sending an array with size != 2
  NSArray* tooManyArgs =
    [greetings arrayByAddingObject:@"Oh no"];
  mGreeter* boom = [[mGreeter alloc]
    initWithPairStringInt:tooManyArgs];
  // Should throw exception before
  assert(NO);
} @catch(NSException* error) {
  assert([[error name] isEqualToString:@"TypeException"]);
  NSString* reason =
    @"The array passed does not match the number of types in a pair. Expected: 2, Got: 3.";
  assert([[error reason] isEqualToString:reason]);
}

std::set


#include <set>
#include <string>

std::set<std::string> getLanguages() {
    return {"English", "Spanish"};
}


// std::set corresponds to NSOrderedSet
NSOrderedSet* languages = [m getLanguages];
assert([languages count] == 2);
assert([languages containsObject:@"English"]);
assert([languages containsObject:@"Spanish"]);

std::tuple


#include <string>
#include <tuple>

std::tuple<int, std::string> sumInts(std::tuple<int, int, std::string> t) {
  // Sum the first two elements
  return {
    std::get<0>(t) + std::get<1>(t),
    std::get<2>(t)
  };
}


// std::tuple corresponds to a NSArray
// with the same amount of values
NSArray* toSum = [NSArray
  arrayWithObjects:@(1), @(2), @"Hello", nil];
assert([toSum count] == 3);

// Sending a tuple to a function
NSArray* summed = [m sumInts:toSum];
assert([summed count] == 2);
assert([[summed objectAtIndex:0] intValue] == 3);
assert([[summed objectAtIndex:1] isEqualToString:@"Hello"]);

// Error handling
@try {
  // Sending an array with size != 3
  NSArray* tooManyArgs =
    [toSum arrayByAddingObject:@"Boom"];
  [m sumInts:tooManyArgs];
  // Should throw exception before
  assert(NO);
} @catch(NSException* error) {
  assert([[error name] isEqualToString:@"TypeException"]);
  NSString* reason =
    @"The array passed does not match the number of types expected in the tuple. Expected: 3, Got: 4.";
  assert([[error reason] isEqualToString:reason]);
}

std::unordered_map


#include <string>
#include <unordered_map>

std::unordered_map<std::string, int>
getUnordered() {
  return {{"Unordered", 1}};
}


// std::unordered_map translates to a NSDictionary
NSDictionary* dict = [m getUnordered];
assert([dict count] == 1);
NSNumber* n = [dict objectForKey:@"Unordered"];
assert(n != nil);
assert([n intValue] == 1);

std::unordered_set


#include <string>
#include <unordered_set>

std::unordered_set<std::string> getLanguages() {
    return {"C++", "Objective-C"};
}


// std::unordered_set corresponds to NSSet
NSSet* languages = [m getLanguages];
assert([languages count] == 2);
assert([languages containsObject:@"C++"]);
assert([languages containsObject:@"Objective-C"]);

std::valarray


#include <valarray>

std::valarray<int> getIt() {
    return {0, 1, 2};
}


// std::valarray corresponds to NSArray
NSArray* v = [m getIt];
assert([v count] == 3);

// The vector contains {0, 1, 2}
assert([[v objectAtIndex:0] intValue] == 0);
assert([[v objectAtIndex:1] intValue] == 1);
assert([[v objectAtIndex:2] intValue] == 2);

std::vector


#include <algorithm>
#include <vector>

std::vector<int> f() {
  return {0, 1, 2};
}

bool allOf(std::vector<bool> const& conditions) {
  return std::all_of(
      conditions.begin(), conditions.end(),
      [](auto c) { return c; });
}

double sum(std::vector<double> const& numbers) {
  double sum = 0;
  for (double number : numbers) {
    sum += number;
  }
  return sum;
}



// std::vector corresponds to NSArray
NSArray* v = [m f];
assert([v count] == 3);

// The vector contains {0, 1, 2}
assert([[v objectAtIndex:0] intValue] == 0);
assert([[v objectAtIndex:1] intValue] == 1);
assert([[v objectAtIndex:2] intValue] == 2);

// Sending NSArray into function works as well
NSArray* conditions = @[@(YES), @(YES), @(NO)];
assert([m allOf:conditions] == NO);

NSArray<NSNumber*>* toSum = @[@(1.1), @(2.2), @(3.3)];
assert([m sum:toSum] == 6.6);