Coding Challenges - The Solutions

Challenge #1: Color Channel Picker

2015-08-03
Solution: char foo(int bar) { return "rgb"[bar]; }
Lesson: You can do array-style indexing on strings in most reasonable languages. However, what you might not have noticed, is you can also do this on string literals, because they work exactly the same.


Challenge #2: Floating-point Negativity

2015-09-08
Solutions:
return n != abs(n);
return sign(n) == -1;
return n.toString()[0] == '-';
return *(uint32_t*)&n >> 31;
Post-Mortem: There are several ways of approaching this one, and it was fun for me to see other people coming up with things I hadn't thought of. It's an interesting test to see how people think. I'm quite low-level-minded and so the bit-shift was my first instinct. Other people approached it differently and came up with the more logical ones. The string check idea comes from this tweet, admittedly the inspiration for this challenge.


Challenge #3: Copy and Assign

2015-09-22
Solution:

constructor
copy constructor
copy constructor

Lesson: This highlights an important difference between copy constructors and assignment operators. Notably, the assignment operator will only be used if the left-hand operand has already been constructed. If you assign to an object when declaring it, the copy constructor will be invoked instead.

Foo a;      // constructs a
Foo b(a);   // copy-constructs b from a
Foo c = a;  // copy-constructs c from a
c = b;      // assigns b to c

If your type has a constructor with a single parameter of a different type, you can also construct it using the assignment syntax, as follows:

class Foo
{
public:
    Foo(int bar);
};

int main()
{
    Foo qux = 5;
    return 0;
}

Challenge #4: Untitled

2015-09-26
Answer: With MSVC you'll either see 0xCDCDCDCD, 0xCCCCCCCC, 0xBAADF00D, or just random numbers, depending on your build settings.
GCC with default settings and you'll see random numbers, but if you turn on optimizations then you'll get 0x00000000.
Clang 3.5 will give you 0x00000000 with default settings, and random numbers if you enable optimizations.
I tested MSVC on my Windows rig and GCC/Clang on my Ubuntu server.
Lesson: I need to write this bit still, sorry! Basically since you're providing an explicit constructor but not manually initializing bar, its value is undefined. Some compilers with certain settings will fill it with a default value for you, but for safety you should generally assume that they won't, and always initialize your memory.


Challenge #5: Operators

2015-10-04
Solution:

class Vec3
{
public:
    Vec3(float xyz) : x(xyz), y(xyz), z(xyz) { }  // a new single-parameter constructor
    Vec3(float x, float y, float z) : x(x), y(y), z(z) { }
    float x, y, z;
};
// and a single multiplication operator
static const Vec3 operator*(const Vec3& a, const Vec3& b)
{
    return Vec3(a.x * b.x, a.y * b.y, a.z * b.z);
}

int main()
{
    Vec3 foo(1.f, 2.f, 3.f);
    Vec3 vf = foo * 5.f; // Vec3(5.f, 10.f, 15.f)
    Vec3 fv = 5.f * foo; // Vec3(5.f, 10.f, 15.f)
    Vec3 vv = foo * foo; // Vec3(1.f,  4.f,  9.f)
    return 0;
}

Lesson: In C++, if you define a constructor with exactly one parameter, it can serve two purposes. It can be used as a traditional constructor (Vec3 qux(5.f)), or it can provide an implicit conversion from the parameter type to the class type. In this case, the added constructor will implicitly convert a float into a Vec3 where the latter is required and the former is provided. Because of this, we only need to define a single multiplication operator that takes two Vec3s, as any float * Vec3 calls will implicitly convert the float to a Vec3.

It's worth noting that this only works if the operator is defined as a non-member function. If the operator was defined as const Vec3 operator*(const Vec3& b), as a member function of Vec3, it would only promote the float to a Vec3 on the right-hand-side of the expression. A float on the left-hand-side would not be promoted.


Challenge #6: Inconsistency

2015-10-07
Answer: It's the backslash in the #include "foo\bar.h" statement.
Lesson: For reasons I won't bore you with here, MS-DOS 2.0 uses the backslash as the default directory separator. Windows (which was originally built on DOS) kept this for legacy compatibility reasons, but also supports using a forward-slash as a directory separator. Unix-based operating systems, on the other hand, have always used a forward-slash as the only directory separator. So when you type #include "foo\bar.h", you're at the mercy of your operating system with regard to how that path is evaluated. If you're on a system that supports backslashes as the directory separator (Windows), that code snippet will compile. If you're on a system that does not (Mac & Linux), then your compiler will look for a file literally named foo\bar.h1. And upon not finding it, you'll get a lovely "No such file or directory" fatal error. The "safe" approach is to always use forward-slashes in your paths, and pray that you never run into a system where they're not supported.

1 Yes, that's actually a thing you can have: