On Monday, 6 April 2026 at 19:08:53 UTC, matheus wrote:
...

I ask deepseek and got the answer and sharing for the future:

Calling Functions Without Branches in D

In D, you can call functions without branches using several techniques. Here's a comprehensive guide:

1. Function Pointer Tables (Direct Lookup)

The most common branchless function calling method - use an array index instead of conditionals.
d

import std.stdio;

// Define functions
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b != 0 ? a / b : 0; }

void main()
{
    // Array of function pointers - branchless dispatch
int delegate(int, int)[] operations = [&add, &sub, &mul, &div];

    int opCode = 2;  // 0=add, 1=sub, 2=mul, 3=div

    // NO branch - direct table lookup and call
    int result = operations[opCode](10, 5);

    writeln(result);  // 50 (mul)
}

2. Using final Switch (Compile-Time Optimized)

D's final switch with an enum can be optimized to a jump table:
d

import std.stdio;

enum Operation { add, sub, mul, div }

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b != 0 ? a / b : 0; }

int execute(Operation op, int a, int b)
{
    final switch (op) {  // Compiler can optimize to jump table
        case Operation.add: return add(a, b);
        case Operation.sub: return sub(a, b);
        case Operation.mul: return mul(a, b);
        case Operation.div: return div(a, b);
    }
}

void main()
{
    // The compiler may generate branchless code here
    int result = execute(Operation.mul, 10, 5);
    writeln(result);  // 50
}

3. Template-Based Compile-Time Selection

Zero runtime overhead - function selection happens at compile time:
d

import std.stdio;

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

// Template that selects function at compile time
auto execute(alias Func, T...)(T args)
{
    return Func(args);
}

void main()
{
    // These calls are resolved at compile time - NO branches
    int r1 = execute!add(10, 5);     // Direct call to add
    int r2 = execute!sub(10, 5);     // Direct call to sub
    int r3 = execute!mul(10, 5);     // Direct call to mul

    writeln(r1, " ", r2, " ", r3);  // 15 5 50
}

4. Delegate Arrays with Compile-Time Construction

Pre-build delegate arrays for OOP-style branchless dispatch:
d

import std.stdio;

class Handler
{
    void process(int x) { writeln("Handler: ", x); }
}

class Logger
{
    void process(int x) { writeln("Logging: ", x); }
}

void main()
{
    auto handler = new Handler();
    auto logger = new Logger();

    // Array of delegates - branchless dispatch
    void delegate(int)[] processors = [
        (int x) => handler.process(x),
        (int x) => logger.process(x)
    ];

    int type = 1;  // Select at runtime

    // NO if/else - direct table lookup
    processors[type](42);  // Output: Logging: 42
}

5. Using std.functional for Compile-Time Dispatch
d

import std.functional;
import std.stdio;

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

// Compose functions at compile time
alias Add5 = curry!(add, 5);
alias Mul10 = curry!(mul, 10);

void main()
{
    // All resolved at compile time - branchless
    int r1 = Add5(3);    // 8
    int r2 = Mul10(7);   // 70

    writeln(r1, " ", r2);
}

6. Virtual Function Table (VTBL) - Already Branchless

D's classes use vtables internally, which is naturally branchless:
d

import std.stdio;

interface Operation
{
    int execute(int a, int b);
}

class Add : Operation
{
    int execute(int a, int b) { return a + b; }
}

class Mul : Operation
{
    int execute(int a, int b) { return a * b; }
}

void main()
{
    Operation op;
    int choice = 1;  // Runtime decision

    // This if is the ONLY branch (to select object)
    if (choice == 0)
        op = new Add();
    else
        op = new Mul();

    // The virtual call itself is branchless (vtable lookup)
    int result = op.execute(10, 5);  // No branch here!

    writeln(result);  // 50
}

7. Function Pointer from Associative Array

For sparse or non-sequential selections:
d

import std.stdio;

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int power(int a, int b) {
    int r = 1;
    foreach(_; 0..b) r *= a;
    return r;
}

void main()
{
    // Associative array for string-based lookup
    int delegate(int, int)[string] ops = [
        "add": &add,
        "mul": &mul,
        "pow": &power
    ];

    string opName = "pow";

    // Branchless hash lookup + call
    int result = ops[opName](2, 3);
    writeln(result);  // 8
}

Performance Comparison
d

import std.stdio;
import std.datetime;

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

// Branch version
int withBranches(int op, int a, int b)
{
    if (op == 0) return add(a, b);
    if (op == 1) return sub(a, b);
    return mul(a, b);
}

// Branchless table version
int delegate(int, int)[] table = [&add, &sub, &mul];

int branchless(int op, int a, int b)
{
    return table[op](a, b);
}

void main()
{
    const ITERS = 100_000_000;

    // Test branch version
    auto sw = StopWatch.Auto.start;
    int sum1;
    foreach (i; 0..ITERS)
        sum1 += withBranches(i % 3, 10, 5);
    auto time1 = sw.peek;

    // Test branchless version
    sw.reset();
    int sum2;
    foreach (i; 0..ITERS)
        sum2 += branchless(i % 3, 10, 5);
    auto time2 = sw.peek;

    writeln("Branch version: ", time1);
    writeln("Branchless table: ", time2);
}

Key Takeaway
Function pointer tables (delegate[] or function[]) are the most straightforward way to call functions without branches in D. They replace:
d

if (op == 0) add();
else if (op == 1) sub();  // BAD: branches
else mul();

With d:

table[op]();  // GOOD: branchless

This works because array indexing is a constant-time operation with no conditional branches, and the indirect function call through the pointer/delegate is also branchless (the CPU just loads the address and jumps).
  • Doubt Branchless... matheus via Digitalmars-d-learn
    • Re: Doubt B... matheus via Digitalmars-d-learn
    • Re: Doubt B... Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn
      • Re: Dou... matheus via Digitalmars-d-learn
        • Re:... Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn

Reply via email to